From: IOhannes m zmölnig (Debian/GNU) Date: Fri, 22 Aug 2025 08:57:19 +0000 (+0200) Subject: New upstream version 2.7.1+ds X-Git-Tag: archive/raspbian/2.7.1+ds-1+rpi1^2~13^2 X-Git-Url: https://dgit.raspbian.org/%22http:/www.example.com/cgi/%22https://%22Program/%22http:/www.example.com/cgi/%22https:/%22Program?a=commitdiff_plain;h=5bc6c0d9479c0352e0d9a0593c51c20120eed87b;p=jacktrip.git New upstream version 2.7.1+ds --- diff --git a/Dockerfile b/Dockerfile index b5d513a..3a6d3df 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,7 +19,7 @@ FROM registry.fedoraproject.org/fedora:${FEDORA_VERSION} AS builder # install tools require to build jacktrip RUN dnf install -y --nodocs cmake gcc gcc-c++ meson git python3-pyyaml python3-jinja2 glib2-devel jack-audio-connection-kit-devel dbus-devel -ENV QT_VERSION=6.5.3 +ENV QT_VERSION=6.8.3 RUN if [ "$(uname -m)" = "x86_64" ]; then export ARCH=amd64; else export ARCH=arm64; fi \ && curl -L -s -o /root/qt.tar.gz "https://files.jacktrip.org/contrib/qt/qt-${QT_VERSION}-static-linux-${ARCH}.tar.gz" \ && tar -C /opt -xzf /root/qt.tar.gz \ diff --git a/LICENSES/MIT.txt b/LICENSES/MIT.txt index adb686a..a6ead36 100644 --- a/LICENSES/MIT.txt +++ b/LICENSES/MIT.txt @@ -2,7 +2,7 @@ SoundWIRE group at CCRMA, Stanford University. Virtual Studio interface and integration - Copyright (c) 2022-2024 JackTrip Labs, Inc. + Copyright (c) 2022-2025 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/docs/Build/Linux.md b/docs/Build/Linux.md index 9f26216..144624f 100644 --- a/docs/Build/Linux.md +++ b/docs/Build/Linux.md @@ -150,7 +150,7 @@ amd64 static docker buildx build --target=artifact -f linux/Dockerfile.build --output type=local,dest=./ \ --platform linux/amd64 --build-arg BUILD_CONTAINER=ubuntu:20.04 \ --build-arg MESON_ARGS="-Ddefault_library=static -Drtaudio=enabled -Drtaudio:jack=disabled -Drtaudio:default_library=static -Drtaudio:alsa=enabled -Drtaudio:pulse=disabled -Drtaudio:werror=false -Dnogui=true" \ - --build-arg QT_DOWNLOAD_URL=https://files.jacktrip.org/contrib/qt/qt-6.5.3-static-linux-amd64.tar.gz . + --build-arg QT_DOWNLOAD_URL=https://files.jacktrip.org/contrib/qt/qt-6.8.3-static-linux-amd64.tar.gz . ``` arm64 dynamic @@ -165,7 +165,7 @@ arm64 static docker buildx build --target=artifact -f linux/Dockerfile.build --output type=local,dest=./ \ --platform linux/arm64 --build-arg BUILD_CONTAINER=ubuntu:20.04 \ --build-arg MESON_ARGS="-Ddefault_library=static -Drtaudio=enabled -Drtaudio:jack=disabled -Drtaudio:default_library=static -Drtaudio:alsa=enabled -Drtaudio:pulse=disabled -Drtaudio:werror=false -Dnogui=true" \ - --build-arg QT_DOWNLOAD_URL=https://files.jacktrip.org/contrib/qt/qt-6.5.3-static-linux-arm64.tar.gz . + --build-arg QT_DOWNLOAD_URL=https://files.jacktrip.org/contrib/qt/qt-6.8.3-static-linux-arm64.tar.gz . ``` arm32 static diff --git a/docs/Build/Mac.md b/docs/Build/Mac.md index dba52b4..25fd4de 100644 --- a/docs/Build/Mac.md +++ b/docs/Build/Mac.md @@ -118,7 +118,7 @@ If you see something like this, you have successfully installed Jacktrip: git clone --recursive https://github.com/steinbergmedia/vst3sdk mkdir vst3sdk/build cd vst3sdk/build -cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" ../ +cmake -DCMAKE_BUILD_TYPE=Release -DSMTG_CREATE_PLUGIN_LINK=0 -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -DCMAKE_OSX_DEPLOYMENT_TARGET=12 ../ cmake --build . --config Release sudo mkdir -p /opt/vst3sdk sudo cp -r lib/Release /opt/vst3sdk/lib @@ -126,16 +126,9 @@ sudo cp -r bin/Release /opt/vst3sdk/bin sudo cp -r ../base ../pluginterfaces ../public.sdk ../vstgui4 /opt/vst3sdk ``` -VST plugins are not allowed to have any shared library dependencies. If you -are using a shared/dynamic version of the Qt libraries to build JackTrip, -you may need to copy over a few static versions for a few of these so that -the linker can find them: - -``` -sudo cp /opt/qt-6.2.6-static/lib/libQt6Core.a /opt/vst3sdk/lib -sudo cp /opt/qt-6.2.6-static/lib/libQt6Network.a /opt/vst3sdk/lib -sudo cp /opt/qt-6.2.6-static/lib/libQt6BundledPcre2.a /opt/vst3sdk/lib -``` +VST plugins are not allowed to have any shared library dependencies. You +can currently only build it when using a static build of Qt. Note that +this also requires configuring Meson without support for the GUI. When you run `meson setup` use `-Dvst-sdkdir=/path/to/vst3sdk` diff --git a/docs/Build/Windows.md b/docs/Build/Windows.md index 2dc3fa9..d74e5df 100644 --- a/docs/Build/Windows.md +++ b/docs/Build/Windows.md @@ -89,8 +89,8 @@ If you see something like this, you have successfully installed Jacktrip: git clone --recursive https://github.com/steinbergmedia/vst3sdk mkdir vst3sdk/build cd vst3sdk/build -cmake -G "Visual Studio 17 2022" -A x64 -DSMTG_CREATE_PLUGIN_LINK=0 ../ -cmake --build . -DCMAKE_CXX_FLAGS="/MD" --config Release +cmake -G "Visual Studio 17 2022" -A x64 -DSMTG_CREATE_PLUGIN_LINK=0 -DCMAKE_CXX_FLAGS="/MD" ../ +cmake --build . --config Release mkdir c:\vst3sdk xcopy /E lib\Release c:\vst3sdk\lib\ xcopy /E bin\Release c:\vst3sdk\bin\ diff --git a/docs/changelog.yml b/docs/changelog.yml index 82fd0a5..1bf0351 100644 --- a/docs/changelog.yml +++ b/docs/changelog.yml @@ -1,3 +1,25 @@ +- Version: "2.7.1" + Date: 2025-06-30 + Description: + - (added) Including Visual C++ Redistributable for Windows + - (fixed) Windows updater sometimes fails to open the download +- Version: "2.7.0" + Date: 2025-06-29 + Description: + - (added) Audio Bridge Audio Unit for macOS + - (added) VS Mode ability to share specific screens or windows + - (updated) VS Mode reduced bandwidth for small video windows + - (updated) VS Mode enabled disk storage for WebEngine + - (updated) PLC auto headroom adjustments and bug fixes + - (updated) Upgraded all builds to use Qt 6.8.3 + - (updated) Audio Bridge VST3 SDK updated to 3.7.13 + - (updated) Use static Qt build when creating VST3 plugin on OSX + - (updated) Improved filters to blocklist iPhone microphones + - (fixed) VS Mode potential crash when shutting down + - (fixed) VS Mode shows too many options for stereo devices + - (fixed) Potential Audio Bridge deadlocks on Windows + - (fixed) Potential double delete in volume meters + - (fixed) Include man page in Linux shared binary zip file - Version: "2.6.0" Date: 2025-04-22 Description: @@ -67,7 +89,7 @@ - (updated) VS Mode improved messaging while loading studios - (fixed) VS Mode possible failures when loading studios - (fixed) VS Mode studio refresh updated to avoid jumpiness - - (fixed) VS Mode blacklisted iPhone microphone device + - (fixed) VS Mode blocklisted iPhone microphone device - (fixed) Race condition in automatic patching for JACK - (fixed) Missing files in Linux binary zip file - Version: "2.3.1" @@ -112,7 +134,7 @@ - (added) New container images for JackTrip hub server - (fixed) Support for audio interfaces on OSX with multiple channels - (fixed) Hub server crashes when trying to rebind ports - - (fixed) VS Mode blacklisted Generic Low Latency ASIO Driver + - (fixed) VS Mode blocklisted Generic Low Latency ASIO Driver - (fixed) VS Mode inconsistent initial connection state - Version: "2.2.2" Date: 2024-02-09 @@ -178,7 +200,7 @@ - (updated) Improved user experience when using the RtAudio backend - (fixed) Crashes when audio interfaces don't support buffer size - (fixed) Crashes when audio interfaces are unplugged while active - - (fixed) Blacklisting Steinberg Generic ASIO driver due to crashes + - (fixed) Blocklisting Steinberg Generic ASIO driver due to crashes - (fixed) Bugs with Virtual Studio deep links and connections stats - (fixed) VS Settings will now revert back when Cancel is selected - (fixed) VS Mode device levels no longer reset on first registration diff --git a/linux/Dockerfile.build b/linux/Dockerfile.build index e0bdbae..307b143 100644 --- a/linux/Dockerfile.build +++ b/linux/Dockerfile.build @@ -78,10 +78,11 @@ RUN if [ -n "$QT_DOWNLOAD_URL" ]; then \ && export SSL_CERT_FILE=$(python3 -m certifi) \ && meson setup --buildtype release $MESON_ARGS $BUILD_PATH \ && meson compile -C $BUILD_PATH -v \ + && mkdir -p $BUILD_PATH/src/vst3 \ && strip $BUILD_PATH/jacktrip \ && if [ -n "$VST3SDK_DOWNLOAD_URL" ]; then \ - strip $BUILD_PATH/JackTrip.vst3; fi + strip $BUILD_PATH/src/vst3/JackTrip.vst3; fi FROM scratch AS artifact -COPY --from=builder /opt/jacktrip/builddir/jacktrip /opt/jacktrip/builddir/JackTrip.vs[t]3 /opt/jacktrip/builddir/linux/org.jacktrip.JackTrip.desktop / +COPY --from=builder /opt/jacktrip/builddir/jacktrip /opt/jacktrip/builddir/jacktrip.1.g[z] /opt/jacktrip/builddir/src/vst3/JackTrip.vs[t]3 /opt/jacktrip/builddir/linux/org.jacktrip.JackTrip.desktop / diff --git a/linux/README.md b/linux/README.md index 585fa95..072397e 100644 --- a/linux/README.md +++ b/linux/README.md @@ -13,7 +13,7 @@ dnf install -y qt6-qtbase qt6-qtbase-common qt6-qtbase-gui qt6-qtsvg qt6-qtwebso For Debian or Ubuntu: ``` -apt install -y libqt6core6 libqt6gui6 libqt6network6 libqt6widgets6 libqt6qml6 libqt6qmlcore6 libqt6quick6 libqt6quickcontrols2-6 libqt6svg6 libqt6webchannel6 libqt6webengine6-data libqt6webenginecore6 libqt6webenginecore6-bin libqt6webenginequick6 libqt6websockets6 libqt6shadertools6 qt6-qpa-plugins qml6-module-qtquick-controls qml6-module-qtqml-workerscript qml6-module-qtquick-templates qml6-module-qtquick-layouts qml6-module-qt5compat-graphicaleffects qml6-module-qtwebchannel qml6-module-qtwebengine qml6-module-qtquick-window libjack-jackd2-0 librtaudio6 libxcb-cursor0 +apt install -y libqt6core6 libqt6gui6 libqt6network6 libqt6widgets6 libqt6qml6 libqt6qmlcore6 libqt6quick6 libqt6quickcontrols2-6 libqt6svg6 libqt6webchannel6 libqt6webengine6-data libqt6webenginecore6 libqt6webenginecore6-bin libqt6webenginequick6 libqt6websockets6 libqt6shadertools6 qt6-qpa-plugins qml6-module-qtquick-controls qml6-module-qtqml-workerscript qml6-module-qtquick-templates qml6-module-qtquick-layouts qml6-module-qt5compat-graphicaleffects qml6-module-qtwebchannel qml6-module-qtwebengine qml6-module-qtquick-window qml6-module-qtquick-dialogs libjack-jackd2-0 librtaudio6 libxcb-cursor0 ``` To install JackTrip as a Linux desktop application: diff --git a/macos/Info.plist b/macos/Info.plist new file mode 100644 index 0000000..3f8fc28 --- /dev/null +++ b/macos/Info.plist @@ -0,0 +1,51 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + jacktrip + CFBundleIconFile + jacktrip + CFBundleGetInfoString + JackTrip Desktop App + CFBundleIdentifier + %BUNDLEID% + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + %BUNDLENAME% + CFBundleURLTypes + + + CFBundleURLSchemes + + jacktrip + + CFBundleTypeRole + Editor + + + CFBundlePackageType + APPL + CFBundleVersion + %VERSION% + CFBundleShortVersionString + %VERSION% + CFBundleSupportedPlatforms + + MacOSX + + LSMinimumSystemVersion + 12.0 + NSHighResolutionCapable + + NSHumanReadableCopyright + Copyright © 2020 Juan-Pablo Caceres, Chris Chafe, Aaron Wyatt, et al. All rights reserved. + NSMicrophoneUsageDescription + JackTrip requires microphone access to capture audio. + NSCameraUsageDescription + JackTrip requires camera access to capture video. + + diff --git a/macos/JackTrip.app_template/Contents/Info.plist b/macos/JackTrip.app_template/Contents/Info.plist deleted file mode 100644 index f28fbb1..0000000 --- a/macos/JackTrip.app_template/Contents/Info.plist +++ /dev/null @@ -1,67 +0,0 @@ - - - - - BuildMachineOSBuild - 19E287 - CFBundleDevelopmentRegion - en - CFBundleExecutable - jacktrip - CFBundleIconFile - jacktrip - CFBundleIdentifier - %BUNDLEID% - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - %BUNDLENAME% - CFBundleURLTypes - - - CFBundleURLSchemes - - jacktrip - - CFBundleTypeRole - Editor - - - CFBundlePackageType - APPL - CFBundleShortVersionString - %VERSION% - CFBundleSignature - ???? - CFBundleSupportedPlatforms - - MacOSX - - CFBundleVersion - %VERSION% - DTCompiler - com.apple.compilers.llvm.clang.1_0 - DTPlatformBuild - 11E503a - DTPlatformVersion - GM - DTSDKBuild - 19E258 - DTSDKName - macosx10.15 - DTXcode - 1141 - DTXcodeBuild - 11E503a - LSMinimumSystemVersion - 10.13 - NSHighResolutionCapable - - NSHumanReadableCopyright - Copyright © 2020 Juan-Pablo Caceres, Chris Chafe, Aaron Wyatt, et al. All rights reserved. - NSMicrophoneUsageDescription - JackTrip requires microphone access to capture audio. - NSCameraUsageDescription - JackTrip requires camera access to capture video. - - diff --git a/macos/JackTrip.app_template/Contents/PkgInfo b/macos/JackTrip.app_template/Contents/PkgInfo deleted file mode 100644 index bd04210..0000000 --- a/macos/JackTrip.app_template/Contents/PkgInfo +++ /dev/null @@ -1 +0,0 @@ -APPL???? \ No newline at end of file diff --git a/macos/JackTrip.app_template/Contents/Resources/jacktrip.icns b/macos/JackTrip.app_template/Contents/Resources/jacktrip.icns deleted file mode 100644 index 532faa2..0000000 Binary files a/macos/JackTrip.app_template/Contents/Resources/jacktrip.icns and /dev/null differ diff --git a/macos/JackTrip.vst3_template/Contents/Info.plist b/macos/JackTrip.vst3_template/Contents/Info.plist deleted file mode 100644 index 5cb1588..0000000 --- a/macos/JackTrip.vst3_template/Contents/Info.plist +++ /dev/null @@ -1,36 +0,0 @@ - - - - - BuildMachineOSBuild - 19E287 - CFBundleDevelopmentRegion - English - CFBundleExecutable - JackTrip.vst3 - CFBundleGetInfoString - JackTrip Audio Bridge - CFBundleIconFile - jacktrip - CFBundleIdentifier - %BUNDLEID% - CFBundleInfoDictionaryVersion - 6.0 - CFBundleLongVersionString - - CFBundleName - %BUNDLENAME% - CFBundlePackageType - BNDL - CFBundleShortVersionString - - CFBundleSignature - ???? - CFBundleVersion - %VERSION% - CSResourcesFileMapped - - NSHumanReadableCopyright - Copyright © 2024-2025 JackTrip Labs, Inc. - - diff --git a/macos/JackTrip.vst3_template/Contents/PkgInfo b/macos/JackTrip.vst3_template/Contents/PkgInfo deleted file mode 100644 index 19a9cf6..0000000 --- a/macos/JackTrip.vst3_template/Contents/PkgInfo +++ /dev/null @@ -1 +0,0 @@ -BNDL???? \ No newline at end of file diff --git a/macos/PkgInfo b/macos/PkgInfo new file mode 100644 index 0000000..bd04210 --- /dev/null +++ b/macos/PkgInfo @@ -0,0 +1 @@ +APPL???? \ No newline at end of file diff --git a/macos/assemble_app.sh b/macos/assemble_app.sh index fdc07fa..0363cd5 100755 --- a/macos/assemble_app.sh +++ b/macos/assemble_app.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash set -e @@ -17,11 +17,34 @@ KEY_STORE="AC_PASSWORD" TEMP_KEYCHAIN="" USE_DEFAULT_KEYCHAIN=false BINARY="../builddir/jacktrip" -VST_BINARY="../builddir/$APPNAME.vst3" +VST3_BINARY="../buildstatic/src/vst3/$APPNAME.vst3" +AUv2_BINARY="../buildstatic/src/auv2/JackTrip.auv2" +AUv3_BINARY="../buildstatic/src/auv3/JackTrip.auv3" +AUDIO_BRIDGE_IMAGES="../src/images/background.png ../src/images/background_2x.png ../src/images/Sercan_Moog_Knob.png ../src/images/Dual_LED.png" PSI=false OPTIND=1 +function version_string_to_int() { + local version="$1" + local old_ifs="$IFS" + + # Remove anything after a dash + version="${version%%-*}" + + # Split version into major, minor, patch + IFS='.' read -r major minor patch <<< "$version" + IFS="$old_ifs" + + # Default to 0 if any part is missing + major=${major:-0} + minor=${minor:-0} + patch=${patch:-0} + + # Calculate version integer + echo $(( ( major << 16 ) | ( minor << 8 ) | patch )) +} + while getopts ":inhqklc:d:u:p:t:b:" opt; do case $opt in i) @@ -105,11 +128,12 @@ shift $((OPTIND - 1)) [ "$#" -gt 0 ] && APPNAME="$1" [ "$#" -gt 1 ] && BUNDLE_ID="$2" -DYNAMIC_QT=$(otool -L $BINARY | grep QtCore) -DYNAMIC_VS=$(otool -L $BINARY | grep QtQml) +DYNAMIC_QT=$(otool -L $BINARY | grep QtCore || true) +DYNAMIC_VS=$(otool -L $BINARY | grep QtQml || true) +DYNAMIC_AUv3=$(otool -L $AUv3_BINARY | grep QtCore || true) if [[ -n "$DYNAMIC_QT" && -n "$QT_PATH" ]]; then - export DYLD_FRAMEWORK_PATH=$QT_PATH/lib + export DYLD_FRAMEWORK_PATH=$QT_PATH/lib fi VERSION="$($BINARY -v | awk '/VERSION/{print $NF}')" @@ -122,22 +146,29 @@ echo "Building bundle $APPNAME (id: $BUNDLE_ID)" echo "for binary version $VERSION" rm -rf "$APPNAME.app" -[ ! -d "JackTrip.app_template/Contents/MacOS" ] && mkdir JackTrip.app_template/Contents/MacOS -cp -a JackTrip.app_template "$APPNAME.app" -cp -f $BINARY "$APPNAME.app/Contents/MacOS/" -# copy licenses -cp -f ../LICENSE.md "$APPNAME.app/Contents/Resources/" -cp -Rf ../LICENSES "$APPNAME.app/Contents/Resources/" +APP_CONTENTS="$APPNAME.app/Contents" +mkdir -p "$APP_CONTENTS/MacOS" +mkdir -p "$APP_CONTENTS/Resources" +cp PkgInfo "$APP_CONTENTS/" +cp ../LICENSE.md "$APP_CONTENTS/Resources/" +cp -R ../LICENSES "$APP_CONTENTS/Resources/" +cp $BINARY "$APP_CONTENTS/MacOS/" -[ $PSI = true ] && cp jacktrip_alt.icns "$APPNAME.app/Contents/Resources/jacktrip.icns" +if [ $PSI = true ]; then + cp ../src/images/jacktrip_alt.icns "$APP_CONTENTS/Resources/jacktrip.icns" +else + cp ../src/images/jacktrip.icns "$APP_CONTENTS/Resources/jacktrip.icns" +fi if [ -n "$DYNAMIC_QT" ] && [ -z "$DYNAMIC_VS" ]; then - cp "Info_novs.plist" "$APPNAME.app/Contents/Info.plist" + cp "Info_novs.plist" "$APP_CONTENTS/Info.plist" +else + cp "Info.plist" "$APP_CONTENTS/Info.plist" fi -sed -i '' "s/%VERSION%/$VERSION/" "$APPNAME.app/Contents/Info.plist" -sed -i '' "s/%BUNDLENAME%/$APPNAME/" "$APPNAME.app/Contents/Info.plist" -sed -i '' "s/%BUNDLEID%/$BUNDLE_ID/" "$APPNAME.app/Contents/Info.plist" +sed -i '' "s/%VERSION%/$VERSION/" "$APP_CONTENTS/Info.plist" +sed -i '' "s/%BUNDLENAME%/$APPNAME/" "$APP_CONTENTS/Info.plist" +sed -i '' "s/%BUNDLEID%/$BUNDLE_ID/" "$APP_CONTENTS/Info.plist" if [ -n "$DYNAMIC_QT" ]; then QT_VERSION="qt$(echo "$DYNAMIC_QT" | sed -E '1!d;s/.*compatibility version ([0-9]+)\.[0-9]+\.[0-9]+.*/\1/g')" @@ -154,7 +185,7 @@ if [ -n "$DYNAMIC_QT" ]; then exit 1 fi fi - DEPLOY_OPTS="-executable=$APPNAME.app/Contents/MacOS/jacktrip -libpath=$QT_PATH/lib" + DEPLOY_OPTS="-executable=$APP_CONTENTS/MacOS/jacktrip -libpath=$QT_PATH/lib" if [ -n "$DYNAMIC_VS" ]; then DEPLOY_OPTS="$DEPLOY_OPTS -qmldir=../src/vs" fi @@ -163,7 +194,7 @@ if [ -n "$DYNAMIC_QT" ]; then if [ -n "$CERTIFICATE" ]; then # manually sign contents since the macdeployqt built-ins do not work (rpath errors) echo "Signing app contents" - PATHS="$APPNAME.app/Contents/Frameworks $APPNAME.app/Contents/PlugIns $APPNAME.app/Contents/Resources" + PATHS="$APP_CONTENTS/Frameworks $APP_CONTENTS/PlugIns $APP_CONTENTS/Resources" find $PATHS -type f | while read fname; do if [[ -f $fname ]]; then codesign -f -s "$CERTIFICATE" --timestamp --entitlements entitlements.plist --options "runtime" "$fname" @@ -172,28 +203,78 @@ if [ -n "$DYNAMIC_QT" ]; then fi fi -if [ -f "$VST_BINARY" ]; then - echo "Building bundle $APPNAME.vst3 (id: $BUNDLE_ID.vst3)" +if [ -f "$VST3_BINARY" ]; then + echo "Building VST3 plugin" rm -rf "$APPNAME.vst3" - [ ! -d "JackTrip.vst3_template/Contents/MacOS" ] && mkdir JackTrip.vst3_template/Contents/MacOS - [ ! -d "JackTrip.vst3_template/Contents/Resources" ] && mkdir JackTrip.vst3_template/Contents/Resources - [ ! -d "JackTrip.app_template/Contents/Resources" ] && mkdir JackTrip.vst3_template/Contents/Resources - cp -a JackTrip.vst3_template "$APPNAME.vst3" - cp -f $VST_BINARY "$APPNAME.vst3/Contents/MacOS/" - # copy licenses - cp -f ../LICENSE.md "$APPNAME.vst3/Contents/Resources/" - cp -Rf ../LICENSES "$APPNAME.vst3/Contents/Resources/" - cp ../src/vst3/resources/* "$APPNAME.vst3/Contents/Resources/" - sed -i '' "s/%VERSION%/$VERSION/" "$APPNAME.vst3/Contents/Resources/moduleinfo.json" - sed -i '' "s/%VERSION%/$VERSION/" "$APPNAME.vst3/Contents/Info.plist" - sed -i '' "s/%BUNDLENAME%/$APPNAME.vst3/" "$APPNAME.vst3/Contents/Info.plist" - sed -i '' "s/%BUNDLEID%/$BUNDLE_ID.vst3/" "$APPNAME.vst3/Contents/Info.plist" + VST3_CONTENTS="$APPNAME.vst3/Contents" + mkdir -p "$VST3_CONTENTS/MacOS" + mkdir -p "$VST3_CONTENTS/Resources" + cp -f ../src/vst3/Info.plist "$VST3_CONTENTS/" + cp -f ../src/vst3/PkgInfo "$VST3_CONTENTS/" + cp -f ../LICENSE.md "$VST3_CONTENTS/Resources/" + cp -Rf ../LICENSES "$VST3_CONTENTS/Resources/" + cp ../src/vst3/resources/* "$VST3_CONTENTS/Resources/" + cp $AUDIO_BRIDGE_IMAGES "$VST3_CONTENTS/Resources/" + cp -f $VST3_BINARY "$VST3_CONTENTS/MacOS/" + sed -i '' "s/%VERSION%/$VERSION/" "$VST3_CONTENTS/Resources/moduleinfo.json" + sed -i '' "s/%VERSION%/$VERSION/" "$VST3_CONTENTS/Info.plist" + sed -i '' "s/%BUNDLENAME%/$APPNAME.vst3/" "$VST3_CONTENTS/Info.plist" + sed -i '' "s/%BUNDLEID%/$BUNDLE_ID.vst3/" "$VST3_CONTENTS/Info.plist" fi -[ $BUILD_INSTALLER = true ] || exit 0 +AUVERSION="$(version_string_to_int $VERSION)" + +if [ -f "$AUv2_BINARY" ]; then + echo "Building AUv2 plugin" + rm -rf "$APPNAME.component" + AUv2_CONTENTS="$APPNAME.component/Contents" + mkdir -p "$AUv2_CONTENTS/MacOS" + mkdir -p "$AUv2_CONTENTS/Resources" + cp -f ../src/auv2/Info.plist "$AUv2_CONTENTS/" + cp -f ../src/auv2/PkgInfo "$AUv2_CONTENTS/" + cp -f ../LICENSE.md "$AUv2_CONTENTS/Resources/" + cp -Rf ../LICENSES "$AUv2_CONTENTS/Resources/" + cp $AUDIO_BRIDGE_IMAGES "$AUv2_CONTENTS/Resources/" + cp -f $AUv2_BINARY "$AUv2_CONTENTS/MacOS/JackTrip" + sed -i '' "s/%VERSION%/$VERSION/" "$AUv2_CONTENTS/Info.plist" + sed -i '' "s/%AUVERSION%/$AUVERSION/" "$AUv2_CONTENTS/Info.plist" + sed -i '' "s/%BUNDLENAME%/$APPNAME.auv2/" "$AUv2_CONTENTS/Info.plist" + sed -i '' "s/%BUNDLEID%/$BUNDLE_ID.auv2/" "$AUv2_CONTENTS/Info.plist" +fi -# If you have Packages installed, you can build an installer for the newly created app bundle. -[ -z $(which packagesbuild) ] && { echo "Error: You need to have Packages installed to build a package."; exit 1; } +if [ -f "$AUv3_BINARY" ]; then + echo "Building AUv3 plugin" + AUv3_CONTENTS="$APP_CONTENTS/PlugIns/JackTrip.appex/Contents" + mkdir -p "$AUv3_CONTENTS/MacOS" + mkdir -p "$AUv3_CONTENTS/Resources" + cp -f ../src/auv3/Info.plist "$AUv3_CONTENTS/" + cp $AUDIO_BRIDGE_IMAGES "$AUv3_CONTENTS/Resources/" + sed -i '' "s/%VERSION%/$VERSION/" "$AUv3_CONTENTS/Info.plist" + sed -i '' "s/%AUVERSION%/$AUVERSION/" "$AUv3_CONTENTS/Info.plist" + sed -i '' "s/%BUNDLENAME%/$APPNAME.auv3/" "$AUv3_CONTENTS/Info.plist" + sed -i '' "s/%BUNDLEID%/$BUNDLE_ID.auv3/" "$AUv3_CONTENTS/Info.plist" + cp -f $AUv3_BINARY "$AUv3_CONTENTS/MacOS/JackTrip" + if [ -n "$DYNAMIC_AUv3" ]; then + echo "Detected a dynamic AUv3 binary" + $DEPLOY_CMD "$APP_CONTENTS/PlugIns/JackTrip.appex" -executable=$AUv3_CONTENTS/MacOS/JackTrip -libpath=$QT_PATH/lib + if [ -n "$CERTIFICATE" ]; then + echo "Signing dynamic AUv3 plugin" + PATHS="$AUv3_CONTENTS/Frameworks $AUv3_CONTENTS/PlugIns $AUv3_CONTENTS/Resources" + find $PATHS -type f | while read fname; do + if [[ -f $fname ]]; then + codesign -f -s "$CERTIFICATE" --timestamp --entitlements entitlements_appex.plist --options "runtime" "$fname" + fi + done + codesign -f -s "$CERTIFICATE" --timestamp --entitlements entitlements_appex.plist --options "runtime" "$APP_CONTENTS/PlugIns/JackTrip.appex" + fi + else + echo "Detected a static AUv3 binary" + if [ -n "$CERTIFICATE" ]; then + echo "Signing static AUv3 plugin" + codesign -f -s "$CERTIFICATE" --timestamp --entitlements entitlements_appex.plist --options "runtime" "$APP_CONTENTS/PlugIns/JackTrip.appex" + fi + fi +fi if [ $PSI = true ]; then cp "package/postinstall.sh" "package/postinstall.sh.bak" @@ -204,12 +285,21 @@ fi if [ -n "$CERTIFICATE" ]; then echo "Signing $APPNAME.app" codesign -f -s "$CERTIFICATE" --timestamp --entitlements entitlements.plist --options "runtime" "$APPNAME.app" - if [ -f "$VST_BINARY" ]; then + if [ -f "$VST3_BINARY" ]; then echo "Signing $APPNAME.vst3" - codesign -f -s "$CERTIFICATE" --timestamp --entitlements entitlements.plist --options "runtime" "$APPNAME.vst3" + codesign -f -s "$CERTIFICATE" --timestamp --options "runtime" "$APPNAME.vst3" + fi + if [ -f "$AUv2_BINARY" ]; then + echo "Signing $APPNAME.component" + codesign -f -s "$CERTIFICATE" --timestamp --options "runtime" "$APPNAME.component" fi fi +[ $BUILD_INSTALLER = true ] || exit 0 + +# If you have Packages installed, you can build an installer for the newly created app bundle. +[ -z $(which packagesbuild) ] && { echo "Error: You need to have Packages installed to build a package."; exit 1; } + # prepare license LICENSE_PATH="package/license.txt" cat ../LICENSE.md > "$LICENSE_PATH" @@ -229,8 +319,8 @@ cp ../README.md "$README_PATH" sed -i '' "s/# //" "$README_PATH" # remove markdown header perl -ane 'chop;print "\n\n" if(/^\s*$/); map{print "$_ ";}@F;' "$README_PATH" > tmp && mv tmp "$README_PATH" # unwrap lines -if [ -f "$VST_BINARY" ]; then - cp package/JackTrip.pkgproj_template_with_vst3 package/JackTrip.pkgproj +if [[ -f "$VST3_BINARY" && -f "$AUv2_BINARY" ]]; then + cp package/JackTrip.pkgproj_template_with_plugins package/JackTrip.pkgproj else cp package/JackTrip.pkgproj_template package/JackTrip.pkgproj fi diff --git a/macos/entitlements_appex.plist b/macos/entitlements_appex.plist new file mode 100644 index 0000000..ee95ab7 --- /dev/null +++ b/macos/entitlements_appex.plist @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.network.client + + + diff --git a/macos/package/JackTrip.pkgproj_template_with_plugins b/macos/package/JackTrip.pkgproj_template_with_plugins new file mode 100644 index 0000000..16ceedb --- /dev/null +++ b/macos/package/JackTrip.pkgproj_template_with_plugins @@ -0,0 +1,2318 @@ + + + + + PACKAGES + + + MUST-CLOSE-APPLICATION-ITEMS + + MUST-CLOSE-APPLICATIONS + + PACKAGE_FILES + + DEFAULT_INSTALL_LOCATION + / + HIERARCHY + + CHILDREN + + + CHILDREN + + + CHILDREN + + GID + 80 + PATH + Jack + PATH_TYPE + 2 + PERMISSIONS + 509 + TYPE + 2 + UID + 0 + + + BUNDLE_CAN_DOWNGRADE + + BUNDLE_POSTINSTALL_PATH + + PATH + postinstall.sh + PATH_TYPE + 1 + + BUNDLE_PREINSTALL_PATH + + PATH_TYPE + 0 + + CHILDREN + + GID + 80 + PATH + ../%BUNDLENAME%.app + PATH_TYPE + 1 + PERMISSIONS + 493 + TYPE + 3 + UID + 0 + + + CHILDREN + + GID + 80 + PATH + Utilities + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + -1 + UID + 0 + + + GID + 80 + PATH + Applications + PATH_TYPE + 0 + PERMISSIONS + 509 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + bin + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + -1 + UID + 0 + + + CHILDREN + + + CHILDREN + + GID + 80 + PATH + Application Support + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Audio + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Automator + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + ColorPickers + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Documentation + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Extensions + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Filesystems + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 80 + PATH + Fonts + PATH_TYPE + 0 + PERMISSIONS + 1021 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Frameworks + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Input Methods + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Internet Plug-Ins + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + LaunchAgents + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + LaunchDaemons + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + PreferencePanes + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Preferences + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 80 + PATH + Printers + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + PrivilegedHelperTools + PATH_TYPE + 0 + PERMISSIONS + 1005 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + QuickLook + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + QuickTime + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Screen Savers + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Scripts + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Services + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Widgets + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + GID + 0 + PATH + Library + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + + CHILDREN + + GID + 0 + PATH + etc + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + -1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + var + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + -1 + UID + 0 + + + GID + 0 + PATH + private + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + -1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + sbin + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + -1 + UID + 0 + + + CHILDREN + + + CHILDREN + + + CHILDREN + + GID + 0 + PATH + Extensions + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + GID + 0 + PATH + Library + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + GID + 0 + PATH + System + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + + CHILDREN + + GID + 0 + PATH + Shared + PATH_TYPE + 0 + PERMISSIONS + 1023 + TYPE + 1 + UID + 0 + + + GID + 80 + PATH + Users + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + + CHILDREN + + GID + 0 + PATH + bin + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + -1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + include + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + -1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + lib + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + -1 + UID + 0 + + + CHILDREN + + + CHILDREN + + GID + 0 + PATH + bin + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + -1 + UID + 0 + + + GID + 0 + PATH + local + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + -1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + sbin + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + -1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + share + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + -1 + UID + 0 + + + GID + 0 + PATH + usr + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + -1 + UID + 0 + + + GID + 0 + PATH + / + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + PAYLOAD_TYPE + 0 + PRESERVE_EXTENDED_ATTRIBUTES + + SHOW_INVISIBLE + + SPLIT_FORKS + + TREAT_MISSING_FILES_AS_WARNING + + VERSION + 5 + + PACKAGE_SCRIPTS + + POSTINSTALL_PATH + + PATH_TYPE + 0 + + PREINSTALL_PATH + + PATH_TYPE + 0 + + RESOURCES + + + PACKAGE_SETTINGS + + AUTHENTICATION + 1 + CONCLUSION_ACTION + 0 + FOLLOW_SYMBOLIC_LINKS + + IDENTIFIER + %BUNDLEID% + LOCATION + 0 + NAME + %BUNDLENAME% + OVERWRITE_PERMISSIONS + + PAYLOAD_SIZE + -1 + REFERENCE_PATH + + RELOCATABLE + + USE_HFS+_COMPRESSION + + VERSION + %VERSION% + + TYPE + 0 + UUID + 10E1CE8D-C84E-45FC-81DA-B174548AE779 + + + MUST-CLOSE-APPLICATION-ITEMS + + MUST-CLOSE-APPLICATIONS + + PACKAGE_FILES + + DEFAULT_INSTALL_LOCATION + / + HIERARCHY + + CHILDREN + + + CHILDREN + + GID + 80 + PATH + Applications + PATH_TYPE + 0 + PERMISSIONS + 509 + TYPE + 1 + UID + 0 + + + CHILDREN + + + CHILDREN + + GID + 80 + PATH + Application Support + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + + CHILDREN + + + CHILDREN + + + BUNDLE_CAN_DOWNGRADE + + BUNDLE_POSTINSTALL_PATH + + PATH_TYPE + 0 + + BUNDLE_PREINSTALL_PATH + + PATH_TYPE + 0 + + CHILDREN + + GID + 0 + PATH + ../%BUNDLENAME%.vst3 + PATH_TYPE + 1 + PERMISSIONS + 493 + TYPE + 3 + UID + 0 + + + GID + 0 + PATH + VST3 + PATH_TYPE + 2 + PERMISSIONS + 509 + TYPE + 2 + UID + 0 + + + GID + 0 + PATH + Plug-Ins + PATH_TYPE + 2 + PERMISSIONS + 509 + TYPE + 2 + UID + 0 + + + GID + 0 + PATH + Audio + PATH_TYPE + 2 + PERMISSIONS + 509 + TYPE + 2 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Automator + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Documentation + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Extensions + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Filesystems + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Frameworks + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Input Methods + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Internet Plug-Ins + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Keyboard Layouts + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + LaunchAgents + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + LaunchDaemons + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + PreferencePanes + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Preferences + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 80 + PATH + Printers + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + PrivilegedHelperTools + PATH_TYPE + 0 + PERMISSIONS + 1005 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + QuickLook + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + QuickTime + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Screen Savers + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Scripts + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Services + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Widgets + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + GID + 0 + PATH + Library + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + + CHILDREN + + GID + 0 + PATH + Shared + PATH_TYPE + 0 + PERMISSIONS + 1023 + TYPE + 1 + UID + 0 + + + GID + 80 + PATH + Users + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + GID + 0 + PATH + / + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + PAYLOAD_TYPE + 0 + PRESERVE_EXTENDED_ATTRIBUTES + + SHOW_INVISIBLE + + SPLIT_FORKS + + TREAT_MISSING_FILES_AS_WARNING + + VERSION + 5 + + PACKAGE_SCRIPTS + + POSTINSTALL_PATH + + PATH_TYPE + 0 + + PREINSTALL_PATH + + PATH_TYPE + 0 + + RESOURCES + + + PACKAGE_SETTINGS + + AUTHENTICATION + 1 + CONCLUSION_ACTION + 0 + FOLLOW_SYMBOLIC_LINKS + + IDENTIFIER + %BUNDLEID%.vst3 + LOCATION + 0 + NAME + %BUNDLENAME%.vst3 + OVERWRITE_PERMISSIONS + + PAYLOAD_SIZE + -1 + REFERENCE_PATH + + RELOCATABLE + + USE_HFS+_COMPRESSION + + VERSION + %VERSION% + + TYPE + 0 + UUID + 8CAD404A-E34F-469F-93DD-D2D2125F35F5 + + + MUST-CLOSE-APPLICATION-ITEMS + + MUST-CLOSE-APPLICATIONS + + PACKAGE_FILES + + DEFAULT_INSTALL_LOCATION + / + HIERARCHY + + CHILDREN + + + CHILDREN + + GID + 80 + PATH + Applications + PATH_TYPE + 0 + PERMISSIONS + 509 + TYPE + 1 + UID + 0 + + + CHILDREN + + + CHILDREN + + GID + 80 + PATH + Application Support + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + + CHILDREN + + + CHILDREN + + + BUNDLE_CAN_DOWNGRADE + + BUNDLE_POSTINSTALL_PATH + + PATH_TYPE + 0 + + BUNDLE_PREINSTALL_PATH + + PATH_TYPE + 0 + + CHILDREN + + GID + 0 + PATH + ../%BUNDLENAME%.component + PATH_TYPE + 1 + PERMISSIONS + 493 + TYPE + 3 + UID + 0 + + + GID + 0 + PATH + Components + PATH_TYPE + 2 + PERMISSIONS + 509 + TYPE + 2 + UID + 0 + + + GID + 0 + PATH + Plug-Ins + PATH_TYPE + 2 + PERMISSIONS + 509 + TYPE + 2 + UID + 0 + + + GID + 0 + PATH + Audio + PATH_TYPE + 2 + PERMISSIONS + 509 + TYPE + 2 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Automator + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Documentation + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Extensions + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Filesystems + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Frameworks + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Input Methods + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Internet Plug-Ins + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Keyboard Layouts + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + LaunchAgents + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + LaunchDaemons + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + PreferencePanes + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Preferences + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 80 + PATH + Printers + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + PrivilegedHelperTools + PATH_TYPE + 0 + PERMISSIONS + 1005 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + QuickLook + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + QuickTime + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Screen Savers + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Scripts + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Services + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Widgets + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + GID + 0 + PATH + Library + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + + CHILDREN + + GID + 0 + PATH + Shared + PATH_TYPE + 0 + PERMISSIONS + 1023 + TYPE + 1 + UID + 0 + + + GID + 80 + PATH + Users + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + GID + 0 + PATH + / + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + PAYLOAD_TYPE + 0 + PRESERVE_EXTENDED_ATTRIBUTES + + SHOW_INVISIBLE + + SPLIT_FORKS + + TREAT_MISSING_FILES_AS_WARNING + + VERSION + 5 + + PACKAGE_SCRIPTS + + POSTINSTALL_PATH + + PATH_TYPE + 0 + + PREINSTALL_PATH + + PATH_TYPE + 0 + + RESOURCES + + + PACKAGE_SETTINGS + + AUTHENTICATION + 1 + CONCLUSION_ACTION + 0 + FOLLOW_SYMBOLIC_LINKS + + IDENTIFIER + %BUNDLEID%.component + LOCATION + 0 + NAME + %BUNDLENAME%.component + OVERWRITE_PERMISSIONS + + PAYLOAD_SIZE + -1 + REFERENCE_PATH + + RELOCATABLE + + USE_HFS+_COMPRESSION + + VERSION + %VERSION% + + TYPE + 0 + UUID + 666D5334-BC88-4AB8-AC88-FA7C769F72A9 + + + PROJECT + + PROJECT_COMMENTS + + NOTES + + + + PROJECT_PRESENTATION + + BACKGROUND + + APPAREANCES + + DARK_AQUA + + LIGHT_AQUA + + + SHARED_SETTINGS_FOR_ALL_APPAREANCES + + + INSTALLATION_STEPS + + + ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS + ICPresentationViewIntroductionController + INSTALLER_PLUGIN + Introduction + LIST_TITLE_KEY + InstallerSectionTitle + + + ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS + ICPresentationViewReadMeController + INSTALLER_PLUGIN + ReadMe + LIST_TITLE_KEY + InstallerSectionTitle + + + ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS + ICPresentationViewLicenseController + INSTALLER_PLUGIN + License + LIST_TITLE_KEY + InstallerSectionTitle + + + ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS + ICPresentationViewDestinationSelectController + INSTALLER_PLUGIN + TargetSelect + LIST_TITLE_KEY + InstallerSectionTitle + + + ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS + ICPresentationViewInstallationTypeController + INSTALLER_PLUGIN + PackageSelection + LIST_TITLE_KEY + InstallerSectionTitle + + + ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS + ICPresentationViewInstallationController + INSTALLER_PLUGIN + Install + LIST_TITLE_KEY + InstallerSectionTitle + + + ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS + ICPresentationViewSummaryController + INSTALLER_PLUGIN + Summary + LIST_TITLE_KEY + InstallerSectionTitle + + + INTRODUCTION + + LOCALIZATIONS + + + LICENSE + + LOCALIZATIONS + + + LANGUAGE + English + VALUE + + PATH + license.txt + PATH_TYPE + 3 + + + + MODE + 0 + + README + + LOCALIZATIONS + + + LANGUAGE + English + VALUE + + PATH + readme.txt + PATH_TYPE + 3 + + + + + TITLE + + LOCALIZATIONS + + + + PROJECT_REQUIREMENTS + + LIST + + RESOURCES + + ROOT_VOLUME_ONLY + + + PROJECT_SETTINGS + + BUILD_FORMAT + 0 + BUILD_PATH + + PATH + build + PATH_TYPE + 1 + + EXCLUDED_FILES + + + PATTERNS_ARRAY + + + REGULAR_EXPRESSION + + STRING + .DS_Store + TYPE + 0 + + + PROTECTED + + PROXY_NAME + Remove .DS_Store files + PROXY_TOOLTIP + Remove ".DS_Store" files created by the Finder. + STATE + + + + PATTERNS_ARRAY + + + REGULAR_EXPRESSION + + STRING + .pbdevelopment + TYPE + 0 + + + PROTECTED + + PROXY_NAME + Remove .pbdevelopment files + PROXY_TOOLTIP + Remove ".pbdevelopment" files created by ProjectBuilder or Xcode. + STATE + + + + PATTERNS_ARRAY + + + REGULAR_EXPRESSION + + STRING + CVS + TYPE + 1 + + + REGULAR_EXPRESSION + + STRING + .cvsignore + TYPE + 0 + + + REGULAR_EXPRESSION + + STRING + .cvspass + TYPE + 0 + + + REGULAR_EXPRESSION + + STRING + .svn + TYPE + 1 + + + REGULAR_EXPRESSION + + STRING + .git + TYPE + 1 + + + REGULAR_EXPRESSION + + STRING + .gitignore + TYPE + 0 + + + PROTECTED + + PROXY_NAME + Remove SCM metadata + PROXY_TOOLTIP + Remove helper files and folders used by the CVS, SVN or Git Source Code Management systems. + STATE + + + + PATTERNS_ARRAY + + + REGULAR_EXPRESSION + + STRING + classes.nib + TYPE + 0 + + + REGULAR_EXPRESSION + + STRING + designable.db + TYPE + 0 + + + REGULAR_EXPRESSION + + STRING + info.nib + TYPE + 0 + + + PROTECTED + + PROXY_NAME + Optimize nib files + PROXY_TOOLTIP + Remove "classes.nib", "info.nib" and "designable.nib" files within .nib bundles. + STATE + + + + PATTERNS_ARRAY + + + REGULAR_EXPRESSION + + STRING + Resources Disabled + TYPE + 1 + + + PROTECTED + + PROXY_NAME + Remove Resources Disabled folders + PROXY_TOOLTIP + Remove "Resources Disabled" folders. + STATE + + + + SEPARATOR + + + + NAME + %BUNDLENAME% + PAYLOAD_ONLY + + TREAT_MISSING_PRESENTATION_DOCUMENTS_AS_WARNING + + + + TYPE + 0 + VERSION + 2 + + diff --git a/macos/package/JackTrip.pkgproj_template_with_vst3 b/macos/package/JackTrip.pkgproj_template_with_vst3 deleted file mode 100644 index d0be9eb..0000000 --- a/macos/package/JackTrip.pkgproj_template_with_vst3 +++ /dev/null @@ -1,1740 +0,0 @@ - - - - - PACKAGES - - - MUST-CLOSE-APPLICATION-ITEMS - - MUST-CLOSE-APPLICATIONS - - PACKAGE_FILES - - DEFAULT_INSTALL_LOCATION - / - HIERARCHY - - CHILDREN - - - CHILDREN - - - CHILDREN - - GID - 80 - PATH - Jack - PATH_TYPE - 2 - PERMISSIONS - 509 - TYPE - 2 - UID - 0 - - - BUNDLE_CAN_DOWNGRADE - - BUNDLE_POSTINSTALL_PATH - - PATH - postinstall.sh - PATH_TYPE - 1 - - BUNDLE_PREINSTALL_PATH - - PATH_TYPE - 0 - - CHILDREN - - GID - 80 - PATH - ../%BUNDLENAME%.app - PATH_TYPE - 1 - PERMISSIONS - 493 - TYPE - 3 - UID - 0 - - - CHILDREN - - GID - 80 - PATH - Utilities - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - -1 - UID - 0 - - - GID - 80 - PATH - Applications - PATH_TYPE - 0 - PERMISSIONS - 509 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - bin - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - -1 - UID - 0 - - - CHILDREN - - - CHILDREN - - GID - 80 - PATH - Application Support - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Audio - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Automator - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - ColorPickers - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Documentation - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Extensions - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Filesystems - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 80 - PATH - Fonts - PATH_TYPE - 0 - PERMISSIONS - 1021 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Frameworks - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Input Methods - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Internet Plug-Ins - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - LaunchAgents - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - LaunchDaemons - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - PreferencePanes - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Preferences - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 80 - PATH - Printers - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - PrivilegedHelperTools - PATH_TYPE - 0 - PERMISSIONS - 1005 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - QuickLook - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - QuickTime - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Screen Savers - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Scripts - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Services - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Widgets - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - GID - 0 - PATH - Library - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - - CHILDREN - - GID - 0 - PATH - etc - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - -1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - var - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - -1 - UID - 0 - - - GID - 0 - PATH - private - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - -1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - sbin - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - -1 - UID - 0 - - - CHILDREN - - - CHILDREN - - - CHILDREN - - GID - 0 - PATH - Extensions - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - GID - 0 - PATH - Library - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - GID - 0 - PATH - System - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - - CHILDREN - - GID - 0 - PATH - Shared - PATH_TYPE - 0 - PERMISSIONS - 1023 - TYPE - 1 - UID - 0 - - - GID - 80 - PATH - Users - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - - CHILDREN - - GID - 0 - PATH - bin - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - -1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - include - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - -1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - lib - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - -1 - UID - 0 - - - CHILDREN - - - CHILDREN - - GID - 0 - PATH - bin - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - -1 - UID - 0 - - - GID - 0 - PATH - local - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - -1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - sbin - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - -1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - share - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - -1 - UID - 0 - - - GID - 0 - PATH - usr - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - -1 - UID - 0 - - - GID - 0 - PATH - / - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - PAYLOAD_TYPE - 0 - PRESERVE_EXTENDED_ATTRIBUTES - - SHOW_INVISIBLE - - SPLIT_FORKS - - TREAT_MISSING_FILES_AS_WARNING - - VERSION - 5 - - PACKAGE_SCRIPTS - - POSTINSTALL_PATH - - PATH_TYPE - 0 - - PREINSTALL_PATH - - PATH_TYPE - 0 - - RESOURCES - - - PACKAGE_SETTINGS - - AUTHENTICATION - 1 - CONCLUSION_ACTION - 0 - FOLLOW_SYMBOLIC_LINKS - - IDENTIFIER - %BUNDLEID% - LOCATION - 0 - NAME - %BUNDLENAME% - OVERWRITE_PERMISSIONS - - PAYLOAD_SIZE - -1 - REFERENCE_PATH - - RELOCATABLE - - USE_HFS+_COMPRESSION - - VERSION - %VERSION% - - TYPE - 0 - UUID - 10E1CE8D-C84E-45FC-81DA-B174548AE779 - - - MUST-CLOSE-APPLICATION-ITEMS - - MUST-CLOSE-APPLICATIONS - - PACKAGE_FILES - - DEFAULT_INSTALL_LOCATION - / - HIERARCHY - - CHILDREN - - - CHILDREN - - GID - 80 - PATH - Applications - PATH_TYPE - 0 - PERMISSIONS - 509 - TYPE - 1 - UID - 0 - - - CHILDREN - - - CHILDREN - - GID - 80 - PATH - Application Support - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - - CHILDREN - - - CHILDREN - - - BUNDLE_CAN_DOWNGRADE - - CHILDREN - - GID - 0 - PATH - ../%BUNDLENAME%.vst3 - PATH_TYPE - 1 - PERMISSIONS - 493 - TYPE - 3 - UID - 0 - - - GID - 0 - PATH - VST3 - PATH_TYPE - 2 - PERMISSIONS - 509 - TYPE - 2 - UID - 0 - - - GID - 0 - PATH - Plug-Ins - PATH_TYPE - 2 - PERMISSIONS - 509 - TYPE - 2 - UID - 0 - - - GID - 0 - PATH - Audio - PATH_TYPE - 2 - PERMISSIONS - 509 - TYPE - 2 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Automator - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Documentation - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Extensions - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Filesystems - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Frameworks - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Input Methods - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Internet Plug-Ins - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Keyboard Layouts - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - LaunchAgents - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - LaunchDaemons - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - PreferencePanes - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Preferences - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 80 - PATH - Printers - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - PrivilegedHelperTools - PATH_TYPE - 0 - PERMISSIONS - 1005 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - QuickLook - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - QuickTime - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Screen Savers - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Scripts - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Services - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - Widgets - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - GID - 0 - PATH - Library - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - CHILDREN - - - CHILDREN - - GID - 0 - PATH - Shared - PATH_TYPE - 0 - PERMISSIONS - 1023 - TYPE - 1 - UID - 0 - - - GID - 80 - PATH - Users - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - - GID - 0 - PATH - / - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 1 - UID - 0 - - PAYLOAD_TYPE - 0 - PRESERVE_EXTENDED_ATTRIBUTES - - SHOW_INVISIBLE - - SPLIT_FORKS - - TREAT_MISSING_FILES_AS_WARNING - - VERSION - 5 - - PACKAGE_SCRIPTS - - POSTINSTALL_PATH - - PATH_TYPE - 0 - - PREINSTALL_PATH - - PATH_TYPE - 0 - - RESOURCES - - - PACKAGE_SETTINGS - - AUTHENTICATION - 1 - CONCLUSION_ACTION - 0 - FOLLOW_SYMBOLIC_LINKS - - IDENTIFIER - %BUNDLEID%.vst3 - LOCATION - 0 - NAME - %BUNDLENAME%.vst3 - OVERWRITE_PERMISSIONS - - PAYLOAD_SIZE - -1 - REFERENCE_PATH - - RELOCATABLE - - USE_HFS+_COMPRESSION - - VERSION - %VERSION% - - TYPE - 0 - UUID - 8CAD404A-E34F-469F-93DD-D2D2125F35F5 - - - PROJECT - - PROJECT_COMMENTS - - NOTES - - - - PROJECT_PRESENTATION - - BACKGROUND - - APPAREANCES - - DARK_AQUA - - LIGHT_AQUA - - - SHARED_SETTINGS_FOR_ALL_APPAREANCES - - - INSTALLATION_STEPS - - - ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS - ICPresentationViewIntroductionController - INSTALLER_PLUGIN - Introduction - LIST_TITLE_KEY - InstallerSectionTitle - - - ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS - ICPresentationViewReadMeController - INSTALLER_PLUGIN - ReadMe - LIST_TITLE_KEY - InstallerSectionTitle - - - ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS - ICPresentationViewLicenseController - INSTALLER_PLUGIN - License - LIST_TITLE_KEY - InstallerSectionTitle - - - ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS - ICPresentationViewDestinationSelectController - INSTALLER_PLUGIN - TargetSelect - LIST_TITLE_KEY - InstallerSectionTitle - - - ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS - ICPresentationViewInstallationTypeController - INSTALLER_PLUGIN - PackageSelection - LIST_TITLE_KEY - InstallerSectionTitle - - - ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS - ICPresentationViewInstallationController - INSTALLER_PLUGIN - Install - LIST_TITLE_KEY - InstallerSectionTitle - - - ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS - ICPresentationViewSummaryController - INSTALLER_PLUGIN - Summary - LIST_TITLE_KEY - InstallerSectionTitle - - - INTRODUCTION - - LOCALIZATIONS - - - LICENSE - - LOCALIZATIONS - - - LANGUAGE - English - VALUE - - PATH - license.txt - PATH_TYPE - 3 - - - - MODE - 0 - - README - - LOCALIZATIONS - - - LANGUAGE - English - VALUE - - PATH - readme.txt - PATH_TYPE - 3 - - - - - TITLE - - LOCALIZATIONS - - - - PROJECT_REQUIREMENTS - - LIST - - RESOURCES - - ROOT_VOLUME_ONLY - - - PROJECT_SETTINGS - - BUILD_FORMAT - 0 - BUILD_PATH - - PATH - build - PATH_TYPE - 1 - - EXCLUDED_FILES - - - PATTERNS_ARRAY - - - REGULAR_EXPRESSION - - STRING - .DS_Store - TYPE - 0 - - - PROTECTED - - PROXY_NAME - Remove .DS_Store files - PROXY_TOOLTIP - Remove ".DS_Store" files created by the Finder. - STATE - - - - PATTERNS_ARRAY - - - REGULAR_EXPRESSION - - STRING - .pbdevelopment - TYPE - 0 - - - PROTECTED - - PROXY_NAME - Remove .pbdevelopment files - PROXY_TOOLTIP - Remove ".pbdevelopment" files created by ProjectBuilder or Xcode. - STATE - - - - PATTERNS_ARRAY - - - REGULAR_EXPRESSION - - STRING - CVS - TYPE - 1 - - - REGULAR_EXPRESSION - - STRING - .cvsignore - TYPE - 0 - - - REGULAR_EXPRESSION - - STRING - .cvspass - TYPE - 0 - - - REGULAR_EXPRESSION - - STRING - .svn - TYPE - 1 - - - REGULAR_EXPRESSION - - STRING - .git - TYPE - 1 - - - REGULAR_EXPRESSION - - STRING - .gitignore - TYPE - 0 - - - PROTECTED - - PROXY_NAME - Remove SCM metadata - PROXY_TOOLTIP - Remove helper files and folders used by the CVS, SVN or Git Source Code Management systems. - STATE - - - - PATTERNS_ARRAY - - - REGULAR_EXPRESSION - - STRING - classes.nib - TYPE - 0 - - - REGULAR_EXPRESSION - - STRING - designable.db - TYPE - 0 - - - REGULAR_EXPRESSION - - STRING - info.nib - TYPE - 0 - - - PROTECTED - - PROXY_NAME - Optimize nib files - PROXY_TOOLTIP - Remove "classes.nib", "info.nib" and "designable.nib" files within .nib bundles. - STATE - - - - PATTERNS_ARRAY - - - REGULAR_EXPRESSION - - STRING - Resources Disabled - TYPE - 1 - - - PROTECTED - - PROXY_NAME - Remove Resources Disabled folders - PROXY_TOOLTIP - Remove "Resources Disabled" folders. - STATE - - - - SEPARATOR - - - - NAME - %BUNDLENAME% - PAYLOAD_ONLY - - TREAT_MISSING_PRESENTATION_DOCUMENTS_AS_WARNING - - - - TYPE - 0 - VERSION - 2 - - diff --git a/meson.build b/meson.build index 259fa85..f0c21fc 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('jacktrip', ['cpp','c'], - default_options: ['cpp_std=c++17','warning_level=2','optimization=2']) + default_options: ['cpp_std=c++20','warning_level=2','optimization=2']) if get_option('profile') == 'development' application_id = 'org.jacktrip.JackTrip.Devel' @@ -302,8 +302,9 @@ if get_option('default_library') == 'static' static_deps += compiler.find_library('dl', required : true) static_deps += compiler.find_library('glib-2.0', required : true) if qt_version == '6' - # we need a Q_IMPORT_LIBRARY for the openssl backend on linux + static_deps += compiler.find_library('rt', required : true) static_deps += compiler.find_library('dbus-1', required : true) + # we need a Q_IMPORT_LIBRARY for the openssl backend on linux static_deps += compiler.find_library('qopensslbackend', required : true, dirs : [qt_plugindir+'/tls']) static_src += ['src/QtStaticPlugins.cpp'] endif @@ -391,6 +392,24 @@ if host_machine.system() == 'darwin' apple_dep = dependency('appleframeworks', modules : ['foundation','coreaudio']) deps += apple_dep add_languages('objcpp') + + if get_option('default_library') == 'static' + # Audio Unit v2 Plugin (requires static linking and AudioUnitSDK) + # The AudioUnitSDK is automatically added and built as a subproject + audiounitsdk_dep = dependency('AudioUnitSDK') + if audiounitsdk_dep.found() == true + subdir('src/auv2') + endif + # Audio Unit v3 Plugin (supports both, but only build if static linking) + # Not currently working + # subdir('src/auv3') + endif +endif + +# VST3 Plugin +vst_sdkdir = get_option('vst-sdkdir') +if vst_sdkdir != '' + subdir('src/vst3') endif if host_machine.system() == 'darwin' and get_option('novs') == false and get_option('nogui') == false @@ -411,124 +430,6 @@ endif jacktrip = executable('jacktrip', src, qres_files, ui_files, moc_files, include_directories: incdirs, dependencies: deps, link_args: link_args, c_args: c_defines, cpp_args: defines, install: true ) -vst_sdkdir = get_option('vst-sdkdir') -if vst_sdkdir != '' - # adapted from https://github.com/centricular/gstreamer-vst3 - vst_includedir = '@0@/public.sdk/source'.format(vst_sdkdir) - vst_pluginterfaces_includedir = '@0@'.format(vst_sdkdir) - vst_incdirs = [] - vst_incdirs += include_directories('@0@'.format(vst_includedir), is_system: true) - vst_incdirs += include_directories('@0@'.format(vst_pluginterfaces_includedir), is_system: true) - vst_incdirs += include_directories('@0@/vstgui4'.format(vst_pluginterfaces_includedir), is_system: true) - - vst_libdir = get_option('vst-libdir') - if vst_libdir == '' - vst_libdir = vst_sdkdir + '/lib' - endif - libbase_dep = compiler.find_library('base', required : true, dirs : [vst_libdir]) - libsdk_dep = compiler.find_library('sdk', required : true, dirs : [vst_libdir]) - libsdk_common_dep = compiler.find_library('sdk_common', required : true, dirs : [vst_libdir]) - libvstgui_dep = compiler.find_library('vstgui', required : true, dirs : [vst_libdir]) - libvstgui_support_dep = compiler.find_library('vstgui_support', required : true, dirs : [vst_libdir]) - libvstgui_uidescription_dep = compiler.find_library('vstgui_uidescription', required : true, dirs : [vst_libdir]) - libpluginterfaces_dep = compiler.find_library('pluginterfaces', required : true, dirs : [vst_libdir]) - vst_deps = [libbase_dep, libsdk_dep, libsdk_common_dep, libvstgui_dep, libvstgui_uidescription_dep, libvstgui_support_dep, libpluginterfaces_dep] - vst_deps += qt_core_deps - - vst_sources = ['src/vst3/JackTripVSTController.cpp', 'src/vst3/JackTripVSTEntry.cpp', 'src/vst3/JackTripVSTProcessor.cpp'] - - # uncomment for live editor - # vst_sources += ['@0@/vstgui4/vstgui/vstgui_uidescription.cpp'.format(vst_sdkdir), '@0@/vstgui4/vstgui/plugin-bindings/vst3editor.cpp'.format(vst_sdkdir)] - # defines += ['-DVSTGUI_LIVE_EDITING=1'] - - vst_link_args = [] - if (host_machine.system() == 'linux') - vst_sources += '@0@/main/linuxmain.cpp'.format(vst_includedir) - vst_deps += static_deps - vst_sources += static_src - vst_link_args += static_link_args - vst_deps += compiler.find_library('xcb-util', required : true) - vst_deps += compiler.find_library('xcb-cursor', required : true) - vst_deps += compiler.find_library('xkbcommon-x11', required : true) - vst_deps += compiler.find_library('xml2', required : true) - vst_deps += compiler.find_library('cairo', required : true) - vst_deps += compiler.find_library('pango-1.0', required : true) - vst_deps += compiler.find_library('pangocairo-1.0', required : true) - vst_deps += compiler.find_library('expat', required : true) - vst_deps += compiler.find_library('fontconfig', required : true) - elif (host_machine.system() == 'darwin') - vst_sources += '@0@/main/macmain.cpp'.format(vst_includedir) - vst_link_args += ['-framework', 'CoreServices'] - vst_link_args += ['-framework', 'CFNetwork'] - vst_link_args += ['-framework', 'AppKit'] - vst_link_args += ['-framework', 'IOKit'] - vst_link_args += ['-framework', 'Security'] - vst_link_args += ['-framework', 'GSS'] - vst_link_args += ['-framework', 'SystemConfiguration'] - vst_deps += dependency('zlib', required : true) - if (qt_version == '5') - vst_link_args += '@0@/libQt5Core.a'.format(vst_libdir) - vst_link_args += '@0@/libQt5Network.a'.format(vst_libdir) - else - vst_link_args += '@0@/libQt6Core.a'.format(vst_libdir) - vst_link_args += '@0@/libQt6Network.a'.format(vst_libdir) - vst_link_args += '@0@/libQt6BundledPcre2.a'.format(vst_libdir) - endif - elif (host_machine.system() == 'windows') - vst_sources += '@0@/main/dllmain.cpp'.format(vst_includedir) - vst_deps += static_deps - vst_sources += static_src - vst_link_args += static_link_args - vst_deps += compiler.find_library('bcrypt', required : true) - vst_deps += compiler.find_library('winmm', required : true) - vst_deps += compiler.find_library('Crypt32', required : true) - vst_deps += compiler.find_library('ws2_32', required: true) - vst_link_args += 'userenv.lib' - vst_link_args += 'Synchronization.lib' - vst_link_args += 'Netapi32.lib' - vst_link_args += 'Version.lib' - vst_link_args += 'Dwrite.lib' - vst_link_args += 'Iphlpapi.lib' - vst_link_args += 'Secur32.lib' - vst_link_args += 'Winhttp.lib' - vst_link_args += 'Dnsapi.lib' - vst_link_args += 'Iphlpapi.lib' - else - error('Unsupported platform: ' + host_machine.system()) - endif - - if found_libsamplerate - vst_deps += libsamplerate_dep - endif - - audio_socket_moc_h = ['src/AudioSocket.h', 'src/SocketClient.h', 'src/ProcessPlugin.h'] - audio_socket_sources = qt.compile_moc(headers: audio_socket_moc_h, extra_args: defines) - audio_socket_sources += [ - 'src/AudioSocket.cpp', - 'src/SocketClient.cpp', - 'src/ProcessPlugin.cpp', - 'src/jacktrip_globals.cpp' - ] - audio_socket_test = executable('audio_socket_tests', - ['tests/audio_socket_test.cpp'] + audio_socket_sources, - cpp_args : defines, - dependencies : vst_deps, - include_directories: vst_incdirs + include_directories('src/'), - link_args: vst_link_args - ) - - vst3 = shared_module('JackTrip', - vst_sources, audio_socket_sources, - name_prefix: '', - name_suffix: 'vst3', - cpp_args : defines, - dependencies : vst_deps, - include_directories: vst_incdirs, - link_args: vst_link_args, - cpp_args: defines - ) -endif - help2man = find_program('help2man', required: false) if (host_machine.system() == 'linux') if help2man.found() diff --git a/src/AudioBridgeProcessor.cpp b/src/AudioBridgeProcessor.cpp new file mode 100644 index 0000000..19e93bd --- /dev/null +++ b/src/AudioBridgeProcessor.cpp @@ -0,0 +1,250 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2024-2025 JackTrip Labs, Inc. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +#include "AudioBridgeProcessor.h" + +#include +#include +#include + +using namespace std; + +// uncomment to generate log file, for debugging purposes +// #define AUDIO_BRIDGE_PLUGIN_LOG + +#ifdef AUDIO_BRIDGE_PLUGIN_LOG +#if defined(_WIN32) +#define AUDIO_BRIDGE_PLUGIN_LOG_PATH "c:/JackTripTemp" +#define AUDIO_BRIDGE_PLUGIN_LOG_FILE "c:/JackTripTemp/plugin.log" +#else +#define AUDIO_BRIDGE_PLUGIN_LOG_PATH "/tmp/jacktrip" +#define AUDIO_BRIDGE_PLUGIN_LOG_FILE "/tmp/jacktrip/plugin.log" +#endif +#include +#include +#include + +static ofstream kLogFile; + +void qtMessageHandler([[maybe_unused]] QtMsgType type, + [[maybe_unused]] const QMessageLogContext& context, + const QString& msg) +{ + kLogFile << msg.toStdString() << endl; +} +#endif + +// any multiplier less than this is considered to be silent +constexpr double kSilentMul = 0.0000001; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// AudioBridgeProcessor +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +AudioBridgeProcessor::AudioBridgeProcessor() {} + +AudioBridgeProcessor::~AudioBridgeProcessor() +{ + uninitialize(); +} + +void AudioBridgeProcessor::initialize(unsigned int sampleRate, unsigned int bufferSize) +{ + // Allocate audio buffers + if (!mBuffersInitialized) { + mInputBuffer = new float*[AudioSocketNumChannels]; + mOutputBuffer = new float*[AudioSocketNumChannels]; + for (int i = 0; i < AudioSocketNumChannels; i++) { + mInputBuffer[i] = new float[AudioSocketMaxSamplesPerBlock]; + mOutputBuffer[i] = new float[AudioSocketMaxSamplesPerBlock]; + } + mBuffersInitialized = true; + } + +#ifdef AUDIO_BRIDGE_PLUGIN_LOG + // Setup plugin logging + if (!filesystem::is_directory(AUDIO_BRIDGE_PLUGIN_LOG_PATH)) { + if (!filesystem::create_directory(AUDIO_BRIDGE_PLUGIN_LOG_PATH)) { + qDebug() << "Failed to create AU log directory: " + << AUDIO_BRIDGE_PLUGIN_LOG_PATH; + } + } + kLogFile.open(AUDIO_BRIDGE_PLUGIN_LOG_FILE, ios::app); + if (kLogFile.is_open()) { + kLogFile << "JackTrip audio bridge plugin initialized" << endl; + kLogFile.flush(); + cout.rdbuf(kLogFile.rdbuf()); + cerr.rdbuf(kLogFile.rdbuf()); + } else { + qDebug() << "Failed to open plugin log file: " << AUDIO_BRIDGE_PLUGIN_LOG_FILE; + } + qInstallMessageHandler(qtMessageHandler); +#endif + + // Initialize AudioSocket + mSocketPtr = make_unique(); + mSocketPtr->setRetryConnection(true); + mSocketPtr->connect(sampleRate, bufferSize); +} + +void AudioBridgeProcessor::uninitialize(void) +{ + // Clean up AudioSocket + mSocketPtr.reset(); + + // Clean up audio buffers + if (mInputBuffer) { + for (int i = 0; i < AudioSocketNumChannels; i++) { + delete[] mInputBuffer[i]; + } + delete[] mInputBuffer; + mInputBuffer = nullptr; + } + + if (mOutputBuffer) { + for (int i = 0; i < AudioSocketNumChannels; i++) { + delete[] mOutputBuffer[i]; + } + delete[] mOutputBuffer; + mOutputBuffer = nullptr; + } + + mBuffersInitialized = false; + +#ifdef AUDIO_BRIDGE_PLUGIN_LOG + if (kLogFile.is_open()) { + kLogFile.close(); + } +#endif +} + +void AudioBridgeProcessor::process(float** inputBuffers, float** outputBuffers, + bool* inputSilenceFlags, bool* outputSilenceFlags, + unsigned int bufSize) +{ + // Check if buffers are initialized + if (!mBuffersInitialized || !mInputBuffer || !mOutputBuffer) { + // sanity check for OOB memory access + if (outputBuffers == nullptr) { + return; + } + // internal buffers not initialized - silence output + for (unsigned int ch = 0; ch < AudioSocketNumChannels; ch++) { + if (outputBuffers[ch] != nullptr) { + memset(outputBuffers[ch], 0, BytesPerSample * bufSize); + } + if (outputSilenceFlags) { + outputSilenceFlags[ch] = true; + } + } + return; + } + + // Limit frames to our max buffer size (sanity check for OOB memory access) + const unsigned int framesToProcess = + min(bufSize, static_cast(AudioSocketMaxSamplesPerBlock)); + + // Clear our internal buffers + for (int ch = 0; ch < AudioSocketNumChannels; ch++) { + memset(mInputBuffer[ch], 0, framesToProcess * BytesPerSample); + memset(mOutputBuffer[ch], 0, framesToProcess * BytesPerSample); + if (outputSilenceFlags) { + outputSilenceFlags[ch] = true; + } + } + + // Copy input to our buffer, applying send gain + if (inputBuffers) { + for (int ch = 0; ch < AudioSocketNumChannels; ch++) { + float* inData = inputBuffers[ch]; + if (inData != nullptr + && (inputSilenceFlags == nullptr || !inputSilenceFlags[ch])) { + for (unsigned int i = 0; i < framesToProcess; i++) { + mInputBuffer[ch][i] = inData[i] * mSendMul; + } + } + } + } + + // Process through AudioSocket + if (mSocketPtr) { + mSocketPtr->compute(framesToProcess, mInputBuffer, mOutputBuffer); + } + + // sanity check for OOB memory access + if (outputBuffers == nullptr) { + return; + } + + // Mix output from AudioSocket with passthrough, applying appropriate gains + for (int ch = 0; ch < AudioSocketNumChannels; ch++) { + float* inData = inputBuffers ? inputBuffers[ch] : nullptr; + float* outData = outputBuffers[ch]; + bool silent = true; + + if (outData == nullptr) + continue; + + for (unsigned int i = 0; i < framesToProcess; i++) { + float output = 0.0f; + + // Add received audio from JackTrip + if (mRecvMul > kSilentMul) { + output += mOutputBuffer[ch][i] * mRecvMul; + } + + // Add passthrough audio + if (mPassMul > kSilentMul && inData + && (inputSilenceFlags == nullptr || !inputSilenceFlags[ch])) { + output += inData[i] * mPassMul; + } + + outData[i] = output; + if (output > 0) { + silent = false; + } + } + + if (outputSilenceFlags) { + outputSilenceFlags[ch] = silent; + } + } +} + +float AudioBridgeProcessor::gainToVol(double gain) +{ + // handle min and max + if (gain < kSilentMul) + return 0; + if (gain > 0.9999999) + return 1.0; + // simple logarithmic conversion + return exp(log(1000) * gain) / 1000.0; +} diff --git a/src/AudioBridgeProcessor.h b/src/AudioBridgeProcessor.h new file mode 100644 index 0000000..6f4e4c6 --- /dev/null +++ b/src/AudioBridgeProcessor.h @@ -0,0 +1,88 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2024-2025 JackTrip Labs, Inc. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +#include + +#include "AudioSocket.h" + +#ifndef __AudioBridgeProcessor_h__ +#define __AudioBridgeProcessor_h__ + +// AudioBridgeProcessor is a class used by audio bridge plugins +// it provides a common interface for audio processing +// and is used to bridge audio between different audio systems +class AudioBridgeProcessor +{ + public: + AudioBridgeProcessor(); + ~AudioBridgeProcessor(); + + // initialize plugin for audio processing + void initialize(unsigned int sampleRate, unsigned int bufferSize); + + // reset plugin to uninitialized state + void uninitialize(void); + + // process audio + // inputBuffers must be null (no input), or an array of size 2 + // outputBuffers must be null (no output), or an array of size 2 + // inputSilenceFlags must be null or an array of size 2 + // outputSilenceFlags must be null or an array of size 2 + // use a null pointer for any channels that are not used + void process(float** inputBuffers, float** outputBuffers, bool* inputSilenceFlags, + bool* outputSilenceFlags, unsigned int bufSize); + + // convert gain setting to volume multiplier + static float gainToVol(double gain); + + // setters and getters for configurable parameters + inline void setSendMul(float mul) { mSendMul = mul; } + inline void setRecvMul(float mul) { mRecvMul = mul; } + inline void setPassMul(float mul) { mPassMul = mul; } + inline float getSendMul(void) { return mSendMul; } + inline float getRecvMul(void) { return mRecvMul; } + inline float getPassMul(void) { return mPassMul; } + inline bool isConnected(void) { return mSocketPtr && mSocketPtr->isConnected(); } + inline bool isEstablished(void) { return mSocketPtr && mSocketPtr->isEstablished(); } + + private: + // Configurable parameters + float mSendMul = 1.0f; + float mRecvMul = 0.0f; + float mPassMul = 1.0f; + + // Audio processing resources + float** mInputBuffer = nullptr; + float** mOutputBuffer = nullptr; + bool mBuffersInitialized = false; + std::unique_ptr mSocketPtr; +}; + +#endif \ No newline at end of file diff --git a/src/AudioSocket.cpp b/src/AudioSocket.cpp index f1f6fb9..268e39f 100644 --- a/src/AudioSocket.cpp +++ b/src/AudioSocket.cpp @@ -38,6 +38,8 @@ #include "AudioSocket.h" #include +#include +#include #include #include "SocketClient.h" @@ -45,13 +47,11 @@ using namespace std; -constexpr int BytesPerSample = sizeof(float); -constexpr int BytesForFullSample = BytesPerSample * AudioSocketNumChannels; - //******************************************************************************* ToAudioSocketPlugin::ToAudioSocketPlugin(AudioSocketQueueT& sendQueue, - AudioSocketQueueT& receiveQueue) - : mSendQueue(sendQueue), mReceiveQueue(receiveQueue) + AudioSocketQueueT& receiveQueue, + AudioSocketWorker& worker) + : mSendQueue(sendQueue), mReceiveQueue(receiveQueue), mWorker(worker) { mSendBuffer.resize(AudioSocketMaxSamplesPerBlock * BytesForFullSample + BytesPerSample); @@ -82,19 +82,12 @@ void ToAudioSocketPlugin::compute(int nframes, float** inputs, init(0, 0); } - if (!mIsConnected) { + if (!mWorker.isInitialized()) { + mWorker.init(getSampleRate(), getBufferSize()); return; } - if (!mSentAudioHeader) { - // send audio socket header - emit signalSendAudioHeader(getSampleRate(), getBufferSize()); - mSentAudioHeader = true; - return; - } - - if (!mRemoteIsReady) { - // waiting to receive audio header + if (!mWorker.isEstablished()) { return; } @@ -115,7 +108,6 @@ void ToAudioSocketPlugin::compute(int nframes, float** inputs, // send the samples to queue mSendQueue.push(reinterpret_cast(mSendBuffer.data())); - emit signalSendAudio(); // note: outputs are ignored } @@ -131,30 +123,19 @@ void ToAudioSocketPlugin::updateNumChannels(int nChansIn, int nChansOut) } //******************************************************************************* -void ToAudioSocketPlugin::remoteIsReady() -{ - mRemoteIsReady = true; -} - -//******************************************************************************* -void ToAudioSocketPlugin::gotConnection() -{ - mSentAudioHeader = false; - mRemoteIsReady = false; - mIsConnected = true; -} - -//******************************************************************************* -void ToAudioSocketPlugin::lostConnection() +void ToAudioSocketPlugin::handleConnectionEstablished() { - mIsConnected = false; + // nothing to do here } //******************************************************************************* FromAudioSocketPlugin::FromAudioSocketPlugin(AudioSocketQueueT& sendQueue, AudioSocketQueueT& receiveQueue, - bool passthrough) - : mSendQueue(sendQueue), mReceiveQueue(receiveQueue), mPassthrough(passthrough) + AudioSocketWorker& worker, bool passthrough) + : mSendQueue(sendQueue) + , mReceiveQueue(receiveQueue) + , mWorker(worker) + , mPassthrough(passthrough) { mRecvBuffer.resize(AudioSocketMaxSamplesPerBlock * BytesForFullSample + BytesPerSample); @@ -205,12 +186,7 @@ void FromAudioSocketPlugin::compute(int nframes, [[maybe_unused]] float** inputs } } - if (!mIsConnected) { - return; - } - - if (!mRemoteIsReady) { - // waiting to receive audio header + if (!mWorker.isEstablished()) { return; } @@ -266,28 +242,14 @@ void FromAudioSocketPlugin::updateNumChannels(int nChansIn, int nChansOut) } //******************************************************************************* -void FromAudioSocketPlugin::remoteIsReady() +void FromAudioSocketPlugin::handleConnectionEstablished() { mNextExtraSample = 0; mLastExtraSample = 0; mQueueCheckSec = 2; - mRemoteIsReady = true; resetQueueStats(); } -//******************************************************************************* -void FromAudioSocketPlugin::gotConnection() -{ - mRemoteIsReady = false; - mIsConnected = true; -} - -//******************************************************************************* -void FromAudioSocketPlugin::lostConnection() -{ - mIsConnected = false; -} - //******************************************************************************* void FromAudioSocketPlugin::updateQueueStats(int nframes) { @@ -330,6 +292,19 @@ void FromAudioSocketPlugin::resetQueueStats() mQueueCheckSec *= 2; } +//******************************************************************************* +AudioSocketWorker::AudioSocketWorker(AudioSocketQueueT& sendQueue, + AudioSocketQueueT& receiveQueue) + : mSendQueue(sendQueue), mReceiveQueue(receiveQueue) +{ + mSendBuffer.resize(AudioSocketMaxSamplesPerBlock * BytesForFullSample + + BytesPerSample); + mRecvBuffer.resize(AudioSocketMaxSamplesPerBlock * BytesForFullSample + + BytesPerSample); + mPopBuffer.resize(AudioSocketMaxSamplesPerBlock * BytesForFullSample + + BytesPerSample); +} + //******************************************************************************* AudioSocketWorker::AudioSocketWorker(AudioSocketQueueT& sendQueue, AudioSocketQueueT& receiveQueue, @@ -342,11 +317,14 @@ AudioSocketWorker::AudioSocketWorker(AudioSocketQueueT& sendQueue, + BytesPerSample); mPopBuffer.resize(AudioSocketMaxSamplesPerBlock * BytesForFullSample + BytesPerSample); + mSocketPtr->moveToThread(this); } //******************************************************************************* AudioSocketWorker::~AudioSocketWorker() { + mStopRequested = true; + wait(); #ifdef HAVE_LIBSAMPLERATE if (mSrcStatePtr != nullptr) { src_delete(mSrcStatePtr); @@ -355,40 +333,91 @@ AudioSocketWorker::~AudioSocketWorker() #endif } -//**************************************************************************** -void AudioSocketWorker::start() +//******************************************************************************* +void AudioSocketWorker::run() { + QMutex wakupMutex; + setRealtimeProcessPriority(); + + while (!mStopRequested) { + if (!isInitialized()) { + QThread::msleep(100); + continue; + } + + if (!isEstablished()) { + if (!connect()) { + if (mStopRequested || !mRetryConnection) { + break; + } + QThread::msleep(250); + continue; + } + } + + if (!sendAudio() || !receiveAudio()) { + if (mStopRequested) { + break; + } + if (!isConnected()) { + // lost audio socket connection + cout << "Lost audio socket connection" << endl; + } else { + cerr << "Audio socket i/o error" << endl; + } + if (!mRetryConnection) { + break; + } + close(); // also updates mIsEstablished + QThread::msleep(250); + continue; + } + + // artificial throttle to prevent CPU from spinning + mSocketPtr->waitForReadyRead(1); + } + + close(); } -//**************************************************************************** -void AudioSocketWorker::connect() +//******************************************************************************* +bool AudioSocketWorker::connect() { - if (isConnected()) { - return; + if (mSocketPtr.isNull()) { + mSocketPtr.reset(new QLocalSocket); + mSocketPtr->moveToThread(this); } - SocketClient c(mSocketPtr); - - if (!c.connect()) { - emit signalConnectionFailed(); - return; + if (!isConnected()) { + SocketClient c(mSocketPtr); + if (!c.connect()) { + return false; + } + if (!c.sendHeader("audio")) { + close(); + return false; + } } - if (!c.sendHeader("audio")) { - mSocketPtr->close(); - emit signalConnectionFailed(); - return; + if (!sendAudioHeader() || !readAudioHeader()) { + close(); + return false; } cout << "Established audio socket connection" << endl; - emit signalConnectionEstablished(); + mIsEstablished = true; + if (mConnectionEstablishedCallback) { + mConnectionEstablishedCallback(); + } + return true; } //******************************************************************************* void AudioSocketWorker::close() { - if (mSocketPtr->state() == QLocalSocket::UnconnectedState + mIsEstablished = false; + if (mSocketPtr.isNull() || mSocketPtr->state() == QLocalSocket::UnconnectedState || mSocketPtr->state() == QLocalSocket::ClosingState) { return; } @@ -397,39 +426,27 @@ void AudioSocketWorker::close() } //******************************************************************************* -void AudioSocketWorker::sendAudioHeader(uint32_t sampleRate, uint16_t bufferSize) +bool AudioSocketWorker::sendAudioHeader() { - mLocalSampleRate = sampleRate; - // send audio socket header QByteArray headerBuffer; headerBuffer.resize(AudioSocketHeaderSize); char* headPtr = headerBuffer.data(); - memcpy(headPtr, &sampleRate, sizeof(uint32_t)); + memcpy(headPtr, &mLocalSampleRate, sizeof(uint32_t)); headPtr += 4; - memcpy(headPtr, &bufferSize, sizeof(uint16_t)); + memcpy(headPtr, &mLocalBufferSize, sizeof(uint16_t)); mSocketPtr->write(headerBuffer); - mSocketPtr->waitForBytesWritten(-1); - - // read audio header from remote to get settings - emit signalReadAudioHeader(); + return mSocketPtr->waitForBytesWritten(-1); } //******************************************************************************* -void AudioSocketWorker::readAudioHeader() +bool AudioSocketWorker::readAudioHeader() { - if (!mSocketPtr->waitForReadyRead(100)) { - // check if connection was lost - if (isConnected()) { - // schedule another attempt - emit signalReadAudioHeader(); - } else { - // lost audio socket connection - cout << "Lost audio socket connection" << endl; - mSocketPtr->disconnect(); - emit signalLostConnection(); + while (!mSocketPtr->waitForReadyRead(100)) { + // check if we are stopping + if (mStopRequested || !isConnected()) { + return false; } - return; } uint32_t headSampleRate; @@ -446,13 +463,11 @@ void AudioSocketWorker::readAudioHeader() // sanity checks (should never happen) if (headSampleRate != 44100 && headSampleRate != 48000 && headSampleRate != 96000) { cerr << "Audio socket received invalid sample rate = " << headSampleRate << endl; - mSocketPtr->close(); - return; + return false; } if (headBufferSize < 2) { cerr << "Audio socket received invalid buffer size = " << headBufferSize << endl; - mSocketPtr->close(); - return; + return false; } cout << "Received audio socket header: sample rate = " << headSampleRate @@ -468,8 +483,7 @@ void AudioSocketWorker::readAudioHeader() if (mSrcStatePtr == nullptr) { cerr << "Failed to prepare sample rate converter: " << src_strerror(srcErr) << endl; - mSocketPtr->close(); - return; + return false; } if (mSrcInDataPtr == nullptr) { mSrcInDataPtr = @@ -490,29 +504,21 @@ void AudioSocketWorker::readAudioHeader() if (mRemoteSampleRate != mLocalSampleRate) { cerr << "Audio socket sample rate conversion not supported: " << mRemoteSampleRate << " != " << mLocalSampleRate << endl; - mSocketPtr->close(); - return; + return false; } #endif - QObject::connect(mSocketPtr.data(), &QLocalSocket::readyRead, this, - &AudioSocketWorker::receiveAudio, Qt::QueuedConnection); - emit signalRemoteIsReady(); + return true; } //******************************************************************************* -void AudioSocketWorker::sendAudio() +bool AudioSocketWorker::sendAudio() { - if (!mSocketPtr->isValid() || mSocketPtr->state() != QLocalSocket::ConnectedState) { - // lost audio socket connection - cout << "Lost audio socket connection" << endl; - mSocketPtr->disconnect(); - emit signalLostConnection(); - return; - } + if (mStopRequested || !isConnected()) + return false; if (mSendQueue.empty()) { - return; + return true; } // send local audio packets to remote @@ -524,13 +530,14 @@ void AudioSocketWorker::sendAudio() memcpy(mSendBuffer.data(), framePtr, bytesToSend); mSocketPtr->write(mSendBuffer); } - mSocketPtr->waitForBytesWritten(-1); + return mSocketPtr->waitForBytesWritten(-1); } //******************************************************************************* -void AudioSocketWorker::receiveAudio() +bool AudioSocketWorker::receiveAudio() { - while (mSocketPtr->bytesAvailable() > BytesForFullSample) { + while (!mStopRequested && isConnected() + && mSocketPtr->bytesAvailable() > BytesForFullSample) { qint64 bytesToRead = mSocketPtr->bytesAvailable(); if (bytesToRead + BytesPerSample > mRecvBuffer.size()) bytesToRead = mRecvBuffer.size() - BytesPerSample; @@ -551,8 +558,7 @@ void AudioSocketWorker::receiveAudio() if (srcErr != 0) { cerr << "Sample rate conversion failure: " << src_strerror(srcErr) << endl; - mSocketPtr->close(); - return; + return false; } mSrcInSamples = mSrcData.input_frames - mSrcData.input_frames_used; if (mSrcInSamples > 0) { @@ -579,149 +585,58 @@ void AudioSocketWorker::receiveAudio() mReceiveQueue.push(reinterpret_cast(mRecvBuffer.data())); } } -} -//******************************************************************************* -void AudioSocketWorker::scheduleReconnect() -{ - if (mRetryConnection) { - cout << "Attempting to reconnect audio socket" << endl; - if (mTimerPtr.isNull()) { - mTimerPtr.reset(new QTimer); - QObject::connect(mTimerPtr.data(), &QTimer::timeout, this, - &AudioSocketWorker::connect); - } - mTimerPtr->start(1000); // try reconnecting in 1 second - } + return true; } //******************************************************************************* AudioSocket::AudioSocket(bool retryConnection) - : mThread() - , mSendQueue(AudioSocketMaxSamplesPerBlock * BytesForFullSample + BytesPerSample) + : mSendQueue(AudioSocketMaxSamplesPerBlock * BytesForFullSample + BytesPerSample) , mReceiveQueue(AudioSocketMaxSamplesPerBlock * BytesForFullSample + BytesPerSample) - , mToAudioSocketPluginPtr(new ToAudioSocketPlugin(mSendQueue, mReceiveQueue)) - , mFromAudioSocketPluginPtr(new FromAudioSocketPlugin(mSendQueue, mReceiveQueue)) { - mThread.setObjectName("AudioSocket"); - mThread.start(); - - QSharedPointer s(new QLocalSocket); - s->moveToThread(&mThread); - - mWorkerPtr.reset(new AudioSocketWorker(mSendQueue, mReceiveQueue, s)); - mWorkerPtr->moveToThread(&mThread); + mWorkerPtr.reset(new AudioSocketWorker(mSendQueue, mReceiveQueue)); mWorkerPtr->setRetryConnection(retryConnection); - - initWorker(); + mWorkerPtr->setObjectName("AudioSocket"); + mWorkerPtr->setConnectionEstablishedCallback([this]() { + handleConnectionEstablished(); + }); + mToAudioSocketPluginPtr.reset( + new ToAudioSocketPlugin(mSendQueue, mReceiveQueue, *mWorkerPtr)); + mFromAudioSocketPluginPtr.reset( + new FromAudioSocketPlugin(mSendQueue, mReceiveQueue, *mWorkerPtr, false)); + mWorkerPtr->start(); } //******************************************************************************* AudioSocket::AudioSocket(QSharedPointer& s) - : mThread() - , mSendQueue(AudioSocketMaxSamplesPerBlock * BytesForFullSample + BytesPerSample) + : mSendQueue(AudioSocketMaxSamplesPerBlock * BytesForFullSample + BytesPerSample) , mReceiveQueue(AudioSocketMaxSamplesPerBlock * BytesForFullSample + BytesPerSample) - , mToAudioSocketPluginPtr(new ToAudioSocketPlugin(mSendQueue, mReceiveQueue)) - , mFromAudioSocketPluginPtr(new FromAudioSocketPlugin(mSendQueue, mReceiveQueue)) - , mWorkerPtr(new AudioSocketWorker(mSendQueue, mReceiveQueue, s)) -{ - mThread.setObjectName("AudioSocket"); - mThread.start(); - - s->moveToThread(&mThread); - mWorkerPtr->moveToThread(&mThread); - - initWorker(); -} - -//******************************************************************************* -void AudioSocket::initWorker() { - auto* toPluginPtr = static_cast(mToAudioSocketPluginPtr.get()); - auto* fromPluginPtr = - static_cast(mFromAudioSocketPluginPtr.get()); - - QObject::connect(this, &AudioSocket::signalConnect, mWorkerPtr.data(), - &AudioSocketWorker::connect, Qt::QueuedConnection); - QObject::connect(this, &AudioSocket::signalClose, mWorkerPtr.data(), - &AudioSocketWorker::close, Qt::QueuedConnection); - QObject::connect(this, &AudioSocket::signalStartWorker, mWorkerPtr.data(), - &AudioSocketWorker::start, Qt::QueuedConnection); - QObject::connect(toPluginPtr, &ToAudioSocketPlugin::signalSendAudioHeader, - mWorkerPtr.data(), &AudioSocketWorker::sendAudioHeader, - Qt::QueuedConnection); - QObject::connect(toPluginPtr, &ToAudioSocketPlugin::signalSendAudio, - mWorkerPtr.data(), &AudioSocketWorker::sendAudio, - Qt::QueuedConnection); - QObject::connect(mWorkerPtr.data(), &AudioSocketWorker::signalRemoteIsReady, - toPluginPtr, &ToAudioSocketPlugin::remoteIsReady, - Qt::DirectConnection); - QObject::connect(mWorkerPtr.data(), &AudioSocketWorker::signalRemoteIsReady, - fromPluginPtr, &FromAudioSocketPlugin::remoteIsReady, - Qt::DirectConnection); - QObject::connect(mWorkerPtr.data(), &AudioSocketWorker::signalConnectionEstablished, - toPluginPtr, &ToAudioSocketPlugin::gotConnection, - Qt::DirectConnection); - QObject::connect(mWorkerPtr.data(), &AudioSocketWorker::signalConnectionEstablished, - fromPluginPtr, &FromAudioSocketPlugin::gotConnection, - Qt::DirectConnection); - QObject::connect(mWorkerPtr.data(), &AudioSocketWorker::signalLostConnection, - toPluginPtr, &ToAudioSocketPlugin::lostConnection, - Qt::DirectConnection); - QObject::connect(mWorkerPtr.data(), &AudioSocketWorker::signalLostConnection, - fromPluginPtr, &FromAudioSocketPlugin::lostConnection, - Qt::DirectConnection); - QObject::connect(mWorkerPtr.data(), &AudioSocketWorker::signalLostConnection, - mWorkerPtr.data(), &AudioSocketWorker::scheduleReconnect, - Qt::DirectConnection); - QObject::connect(mWorkerPtr.data(), &AudioSocketWorker::signalConnectionFailed, - mWorkerPtr.data(), &AudioSocketWorker::scheduleReconnect, - Qt::DirectConnection); - QObject::connect(mWorkerPtr.data(), &AudioSocketWorker::signalReadAudioHeader, - mWorkerPtr.data(), &AudioSocketWorker::readAudioHeader, - Qt::QueuedConnection); - - if (isConnected()) { - toPluginPtr->gotConnection(); - fromPluginPtr->gotConnection(); - } - - emit signalStartWorker(); + mWorkerPtr.reset(new AudioSocketWorker(mSendQueue, mReceiveQueue, s)); + mWorkerPtr->setObjectName("AudioSocket"); + mWorkerPtr->setConnectionEstablishedCallback([this]() { + handleConnectionEstablished(); + }); + mToAudioSocketPluginPtr.reset( + new ToAudioSocketPlugin(mSendQueue, mReceiveQueue, *mWorkerPtr)); + mFromAudioSocketPluginPtr.reset( + new FromAudioSocketPlugin(mSendQueue, mReceiveQueue, *mWorkerPtr, false)); + mWorkerPtr->start(); } //******************************************************************************* AudioSocket::~AudioSocket() { - mThread.quit(); - mThread.wait(); mWorkerPtr.reset(); } //******************************************************************************* -bool AudioSocket::connect(int samplingRate, int bufferSize) +void AudioSocket::connect(int samplingRate, int bufferSize) { - if (mWorkerPtr->isConnected()) { - return true; + if (!mToAudioSocketPluginPtr->getInited()) { + mToAudioSocketPluginPtr->init(samplingRate, bufferSize); + mFromAudioSocketPluginPtr->init(samplingRate, bufferSize); } - - mFromAudioSocketPluginPtr->init(samplingRate, bufferSize); - mToAudioSocketPluginPtr->init(samplingRate, bufferSize); - emit signalConnect(); - - QTimer timer; - timer.setTimerType(Qt::CoarseTimer); - timer.setSingleShot(true); - - QEventLoop loop; - QObject::connect(mWorkerPtr.data(), &AudioSocketWorker::signalConnectionEstablished, - &loop, &QEventLoop::quit); - QObject::connect(mWorkerPtr.data(), &AudioSocketWorker::signalConnectionFailed, &loop, - &QEventLoop::quit); - QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); - timer.start(1000); - loop.exec(); - - return mWorkerPtr->isConnected(); } //******************************************************************************* @@ -732,7 +647,11 @@ void AudioSocket::compute(int nframes, float** inputs, float** outputs) } //******************************************************************************* -void AudioSocket::close() +void AudioSocket::handleConnectionEstablished() { - emit signalClose(); + auto* toPluginPtr = static_cast(mToAudioSocketPluginPtr.get()); + auto* fromPluginPtr = + static_cast(mFromAudioSocketPluginPtr.get()); + toPluginPtr->handleConnectionEstablished(); + fromPluginPtr->handleConnectionEstablished(); } diff --git a/src/AudioSocket.h b/src/AudioSocket.h index 3d1be40..768e421 100644 --- a/src/AudioSocket.h +++ b/src/AudioSocket.h @@ -43,7 +43,7 @@ #include #include #include -#include +#include #include "ProcessPlugin.h" #include "WaitFreeFrameBuffer.h" @@ -64,9 +64,18 @@ constexpr int AudioSocketMaxQueueSize = 1024; // audio header is 4 bytes for the number of samples + 2 bytes for the buffer size constexpr int AudioSocketHeaderSize = 4 + 2; +// number of bytes per audio sample +constexpr int BytesPerSample = sizeof(float); + +// number of bytes per audio sample across all channels +constexpr int BytesForFullSample = BytesPerSample * AudioSocketNumChannels; + // data type for audio socket circular buffer typedef WaitFreeFrameBuffer AudioSocketQueueT; +// forward declations +class AudioSocketWorker; + /** \brief ToAudioSocketPlugin is used to send audio from a signal chain to an audio * socket */ @@ -75,7 +84,8 @@ class ToAudioSocketPlugin : public ProcessPlugin Q_OBJECT; public: - ToAudioSocketPlugin(AudioSocketQueueT& sendQueue, AudioSocketQueueT& receiveQueue); + ToAudioSocketPlugin(AudioSocketQueueT& sendQueue, AudioSocketQueueT& receiveQueue, + AudioSocketWorker& worker); virtual ~ToAudioSocketPlugin(); void init(int samplingRate, int bufferSize) override; @@ -84,24 +94,14 @@ class ToAudioSocketPlugin : public ProcessPlugin void compute(int nframes, float** inputs, float** outputs) override; const char* getName() const override { return "ToAudioSocket"; }; void updateNumChannels(int nChansIn, int nChansOut) override; - - signals: - void signalSendAudioHeader(uint32_t sampleRate, uint16_t bufferSize); - void signalSendAudio(); - - public slots: - void remoteIsReady(); - void gotConnection(); - void lostConnection(); + void handleConnectionEstablished(); private: AudioSocketQueueT& mSendQueue; AudioSocketQueueT& mReceiveQueue; + AudioSocketWorker& mWorker; QByteArray mSendBuffer; - int mNumChannels = AudioSocketNumChannels; - bool mSentAudioHeader = false; - bool mRemoteIsReady = false; - bool mIsConnected = false; + int mNumChannels = AudioSocketNumChannels; }; /** \brief FromAudioSocketPlugin is used mix audio from an audio socket into a signal @@ -113,7 +113,7 @@ class FromAudioSocketPlugin : public ProcessPlugin public: FromAudioSocketPlugin(AudioSocketQueueT& sendQueue, AudioSocketQueueT& receiveQueue, - bool passthrough = false); + AudioSocketWorker& worker, bool passthrough = false); virtual ~FromAudioSocketPlugin(); void init(int samplingRate, int bufferSize) override; @@ -122,13 +122,9 @@ class FromAudioSocketPlugin : public ProcessPlugin void compute(int nframes, float** inputs, float** outputs) override; const char* getName() const override { return "FromAudioSocket"; }; void updateNumChannels(int nChansIn, int nChansOut) override; + void handleConnectionEstablished(); void setPassthrough(bool b) { mPassthrough = b; } - public slots: - void remoteIsReady(); - void gotConnection(); - void lostConnection(); - protected: void updateQueueStats(int nframes); void resetQueueStats(); @@ -136,6 +132,7 @@ class FromAudioSocketPlugin : public ProcessPlugin private: AudioSocketQueueT& mSendQueue; AudioSocketQueueT& mReceiveQueue; + AudioSocketWorker& mWorker; QByteArray mRecvBuffer; float** mExtraSamples = nullptr; int mNumChannels = AudioSocketNumChannels; @@ -145,74 +142,89 @@ class FromAudioSocketPlugin : public ProcessPlugin int mMaxQueuePackets = 0; int mQueueCheckSec = 0; uint32_t mNextQueueCheck = 0; - bool mRemoteIsReady = false; - bool mIsConnected = false; bool mPassthrough = false; }; /** \brief AudioSocketWorker is used to perform socket operations in a separate thread */ -class AudioSocketWorker : public QObject +class AudioSocketWorker : public QThread { Q_OBJECT; public: + AudioSocketWorker(AudioSocketQueueT& sendQueue, AudioSocketQueueT& receiveQueue); AudioSocketWorker(AudioSocketQueueT& sendQueue, AudioSocketQueueT& receiveQueue, QSharedPointer& s); virtual ~AudioSocketWorker(); + /// \brief initializes the local sample rate and buffer size + inline void init(int samplingRate, int bufferSize) + { + mLocalSampleRate = samplingRate; + mLocalBufferSize = bufferSize; + } + + /// \brief sets the retry connection flag inline void setRetryConnection(bool retry) { mRetryConnection = retry; } - inline bool isConnected() + + /// \brief sets the connection established callback + inline void setConnectionEstablishedCallback(std::function callback) { - return mSocketPtr->state() == QLocalSocket::ConnectedState; + mConnectionEstablishedCallback = callback; } - inline QLocalSocket& getSocket() { return *mSocketPtr; } - signals: - void signalReadAudioHeader(); - void signalConnectionEstablished(); - void signalConnectionFailed(); - void signalLostConnection(); - void signalRemoteIsReady(); + /// \brief returns true if the worker is established + inline bool isEstablished() { return mIsEstablished; } - public slots: - // sets a few things up at startup - void start(); + /// \brief returns true if the worker is initialized + inline bool isInitialized() { return mLocalSampleRate != 0 && mLocalBufferSize != 0; } - // attempts to connect to remote instance's socket server - // returns true if connection was successfully established - // returns false and schedules retry if connection failed - void connect(); + /// \brief returns true if the socket is connected + inline bool isConnected() + { + return !mSocketPtr.isNull() && mSocketPtr->isValid() + && mSocketPtr->state() == QLocalSocket::ConnectedState; + } + + protected: + /// \brief override the run method to perform socket operations in a separate thread + virtual void run() override; + + /// \brief connects to the remote instance's socket server + bool connect(); /// \brief closes the connection to remote instance's socket server void close(); /// \brief send audio header to remote instance - void sendAudioHeader(uint32_t sampleRate, uint16_t bufferSize); + bool sendAudioHeader(); /// \brief read audio header from remote instance - void readAudioHeader(); + bool readAudioHeader(); /// \brief sends audio packets to remote instance - void sendAudio(); + bool sendAudio(); /// \brief receives audio bytes from remote instance - void receiveAudio(); + bool receiveAudio(); - /// \brief schedules a reconnect attempt - void scheduleReconnect(); + /// \brief returns the raw local socket + inline QLocalSocket& getSocket() { return *mSocketPtr; } private: AudioSocketQueueT& mSendQueue; AudioSocketQueueT& mReceiveQueue; - QScopedPointer mTimerPtr; QSharedPointer mSocketPtr; + std::function mConnectionEstablishedCallback; QByteArray mSendBuffer; QByteArray mRecvBuffer; QByteArray mPopBuffer; bool mRetryConnection = false; + bool mStopRequested = false; + bool mIsEstablished = false; int mLocalSampleRate = 0; int mRemoteSampleRate = 0; + int mLocalBufferSize = 0; #ifdef HAVE_LIBSAMPLERATE SRC_DATA mSrcData; SRC_STATE* mSrcStatePtr = nullptr; @@ -224,57 +236,61 @@ class AudioSocketWorker : public QObject /** \brief An AudioSocket is used to exchange audio with another processes via a local * socket */ -class AudioSocket : public QObject +class AudioSocket { - Q_OBJECT; - public: + // constructs a disconnected audio socket AudioSocket(bool retryConnection = false); + + // constructs an audio socket with established connection AudioSocket(QSharedPointer& s); + + // destructor virtual ~AudioSocket(); + /// returns true if the socket is established + inline bool isEstablished() { return mWorkerPtr->isEstablished(); } + + /// returns true if the socket is connected inline bool isConnected() { return mWorkerPtr->isConnected(); } - inline QLocalSocket& getSocket() { return mWorkerPtr->getSocket(); } + + /// returns the sample rate inline int getSampleRate() const { return mToAudioSocketPluginPtr->getSampleRate(); } + + /// returns the buffer size inline int getBufferSize() const { return mToAudioSocketPluginPtr->getBufferSize(); } + + /// returns the plugin used for sending audio inline QSharedPointer& getToAudioSocketPlugin() { return mToAudioSocketPluginPtr; } + + /// returns the plugin used for receiving audio inline QSharedPointer& getFromAudioSocketPlugin() { return mFromAudioSocketPluginPtr; } + + /// sets the retry connection flag inline void setRetryConnection(bool retry) { mWorkerPtr->setRetryConnection(retry); } // attempts to connect to remote instance's socket server - // returns true if connection was successfully established - // returns false and schedules retry if connection failed - bool connect(int samplingRate, int bufferSize); + void connect(int samplingRate, int bufferSize); /// \brief audio callback for duplex processing void compute(int nframes, float** inputs, float** outputs); - /// \brief closes the connection to remote instance's socket server - void close(); - - signals: - void signalStartWorker(); - void signalConnect(); - void signalClose(); + protected: + /// \brief handles the connection established callback + void handleConnectionEstablished(); private: - /// \brief initializes worker and worker thread - void initWorker(); - - QThread mThread; AudioSocketQueueT mSendQueue; AudioSocketQueueT mReceiveQueue; QSharedPointer mToAudioSocketPluginPtr; QSharedPointer mFromAudioSocketPluginPtr; QScopedPointer mWorkerPtr; - - friend class AudioSocketWorker; }; #endif \ No newline at end of file diff --git a/src/Meter.cpp b/src/Meter.cpp index f77336b..ad88eec 100644 --- a/src/Meter.cpp +++ b/src/Meter.cpp @@ -43,10 +43,15 @@ #include "jacktrip_types.h" #include "meterdsp.h" +constexpr int kMaxNumChannels = 256; + //******************************************************************************* -Meter::Meter(int numchans, bool verboseFlag) : mNumChannels(numchans) +Meter::Meter(int numchans, bool verboseFlag) + : mNumChannels(std::min(numchans, kMaxNumChannels)) { setVerbose(verboseFlag); + mValues = new float[kMaxNumChannels]; + mOutValues = new float[kMaxNumChannels]; for (int i = 0; i < mNumChannels; i++) { meterP.push_back(new meterdsp); } @@ -60,12 +65,8 @@ Meter::~Meter() delete static_cast(meterP[i]); } meterP.clear(); - if (mValues) { - delete mValues; - } - if (mOutValues) { - delete mOutValues; - } + delete[] mValues; + delete[] mOutValues; if (mBuffer) { delete mBuffer; } @@ -147,9 +148,9 @@ void Meter::updateNumChannels(int nChansIn, int nChansOut) } if (outgoingPluginToNetwork) { - mNumChannels = nChansIn; + mNumChannels = std::min(nChansIn, kMaxNumChannels); } else { - mNumChannels = nChansOut; + mNumChannels = std::min(nChansOut, kMaxNumChannels); } setupValues(); @@ -157,21 +158,6 @@ void Meter::updateNumChannels(int nChansIn, int nChansOut) void Meter::setupValues() { - if (mValues) { - float* oldValues = mValues; - // Delete our old array after 5 seconds. - QTimer::singleShot(5000, this, [=]() { - delete oldValues; - }); - } - if (mOutValues) { - float* oldOut = mOutValues; - QTimer::singleShot(5000, this, [=]() { - delete oldOut; - }); - } - mValues = new float[mNumChannels]; - mOutValues = new float[mNumChannels]; for (int i = 0; i < mNumChannels; i++) { mValues[i] = threshold; mOutValues[i] = threshold; diff --git a/src/Regulator.cpp b/src/Regulator.cpp index 39fe2f4..8b7cf4c 100644 --- a/src/Regulator.cpp +++ b/src/Regulator.cpp @@ -97,7 +97,7 @@ constexpr int HISTFPP = 128; // default FPP when calibrating burg window constexpr int NumSlots = 4096; // NumSlots looped for recent arrivals constexpr double AutoMax = 250.0; // msec bounds on insane IPI, like ethernet unplugged -constexpr double AutoInitDur = 3000.0; // kick in auto after this many msec +constexpr double AutoInitDur = 2000.0; // kick in auto after this many msec constexpr double AutoInitValFactor = 0.5; // scale for initial mMsecTolerance during init phase if unspecified @@ -241,7 +241,6 @@ Channel::Channel(int fpp, int upToNow, int packetsInThePast) // operates at pee tmp[j] = 0.0; mPacketRing.push_back(tmp); } - lastWasGlitch = false; } // push received packet to ring @@ -459,8 +458,11 @@ bool Regulator::enableWorker() // our local audio callback interval (too slow to keep up) const double maxPLCdspAllowed = mLocalFPPdurMsec * 0.7; // 70% if (mStatsMaxPLCdspElapsed >= maxPLCdspAllowed && !isWorkerEnabled()) { - cout << "PLC dsp " << mStatsMaxPLCdspElapsed - << " is too slow (max=" << maxPLCdspAllowed << "), enabling worker" << endl; + if (gVerboseFlag) { + cout << "PLC dsp " << mStatsMaxPLCdspElapsed + << " is too slow (max=" << maxPLCdspAllowed << "), enabling worker" + << endl; + } mWorkerBuffer = new int8_t[mPeerBytes]; memset(mWorkerBuffer, 0, mPeerBytes); mWorkerThreadPtr = new QThread(); @@ -492,21 +494,29 @@ void Regulator::updateTolerance(int glitches, int skipped) mSkipAutoHeadroom = false; } // sanity check: prevent headroom from growing beyond the greater of - // 3x rolling average of max, or 100ms - const int maxHeadroom = std::max(pushStat->longTermMax * 3, 100.0); - // only increase headroom if glitch tolerance was exceeded and doing so - // would have reduced the number of glitches that occured over the past second. - if (skipped > 0 && glitches > glitchesAllowed - && mCurrentHeadroom + 1 <= maxHeadroom) { + // 3x rolling average of max, or 10ms higher than the max latency observed + const int maxHeadroom = + std::max(pushStat->longTermMax * 3, mLastMaxLatency + 10); + // only increase headroom if glitch tolerance was exceeded + if (glitches > glitchesAllowed && mCurrentHeadroom < maxHeadroom) { if (mSkipAutoHeadroom) { mSkipAutoHeadroom = false; } else { // don't increase headroom two intervals in a row mSkipAutoHeadroom = true; - ++mCurrentHeadroom; - cout << "PLC skipped=" << skipped << " glitches=" << glitches << ">" - << glitchesAllowed << ", increasing headroom to " << mCurrentHeadroom - << " (max=" << maxHeadroom << ")" << endl; + if (mLastMaxLatency > mMsecTolerance + 1) { + // increase headroom enough to cover any skipped packets + mCurrentHeadroom = std::min( + maxHeadroom, std::ceil(mLastMaxLatency - mMsecTolerance)); + } else { + ++mCurrentHeadroom; + } + if (gVerboseFlag) { + cout << "PLC skipped=" << skipped << " glitches=" << glitches << ">" + << glitchesAllowed << ", lastmax=" << mLastMaxLatency + << ", increasing headroom to " << mCurrentHeadroom + << " (max=" << maxHeadroom << ")" << endl; + } } } else { // thresholds not met: require 2 intervals in a row @@ -554,8 +564,6 @@ void Regulator::updatePushStats(int seq_num) const int newSkipped = totalSkipped - mLastSkipped; mLastGlitches = totalGlitches; mLastSkipped = totalSkipped; - mLastMaxLatency = mStatsMaxLatency; - mStatsMaxLatency = 0; if (mAuto && pushStat->lastTime > AutoInitDur) { // after AutoInitDur: update auto tolerance once per second if (pushStat->lastTime <= mAutoHeadroomStartTime) { @@ -564,8 +572,11 @@ void Regulator::updatePushStats(int seq_num) // a calculated tolerance. Otherwise, the switch can // sometimes cause it to bump headroom prematurely even // though there are no real audio glitches. + mStatsMaxLatency = 0; // ignore during warmup updateTolerance(0, 0); } else { + mLastMaxLatency = mStatsMaxLatency; // only set after warmup + mStatsMaxLatency = 0; updateTolerance(newGlitches, newSkipped); } } @@ -588,7 +599,7 @@ void Regulator::setQueueBufferLength(int queueBuffer) mAutoHeadroom = -1; mCurrentHeadroom = 0; mSkipAutoHeadroom = true; - mAutoHeadroomStartTime = pushStat ? (pushStat->lastTime + 3000.0) : 3000.0; + mAutoHeadroomStartTime = pushStat ? (pushStat->lastTime + AutoInitDur) : 4000.0; } else { mAutoHeadroom = std::abs(queueBuffer); mCurrentHeadroom = mAutoHeadroom; @@ -604,7 +615,13 @@ void Regulator::pushPacket(const int8_t* buf, int seq_num) // if (seq_num==0) return; // impose regular loss mIncomingTiming[seq_num] = (double)mIncomingTimer.nsecsElapsed() / 1000000.0; memcpy(mSlots[seq_num], buf, mPeerBytes); - mLastSeqNumIn.store(seq_num, std::memory_order_release); + // ensure that last sequnce number is "greater than" the current one + // otherwise, we can create a condition where last out gets ahead of last in + const int lastSeqNumIn = mLastSeqNumIn.load(std::memory_order_relaxed); + if (lastSeqNumIn == -1 || seq_num > lastSeqNumIn + || (seq_num < lastSeqNumIn && (lastSeqNumIn - seq_num) > (NumSlots / 2))) { + mLastSeqNumIn.store(seq_num, std::memory_order_release); + } }; //******************************************************************************* @@ -646,21 +663,19 @@ bool Regulator::pullPacket() if (skipped < 0) skipped += NumSlots; } + // update max latency for every valid packet, including any skipped ones + double latency = (now - mIncomingTiming[next]); + if (latency > mStatsMaxLatency) { + mStatsMaxLatency = latency; + } // check if packet's age matches tolerance, or is the best candidate we have if (mIncomingTiming[next] + mMsecTolerance >= now || i == 0) { // next is the best candidate memcpy(mXfrBuffer, mSlots[next], mPeerBytes); mLastSeqNumOut = next; - double latency = (now - mIncomingTiming[mLastSeqNumOut]); - if (latency > mStatsMaxLatency) { - mStatsMaxLatency = latency; - } goto PACKETOK; } - // track how many good packets we skipped due to tolerance < 1ms - if (mIncomingTiming[next] + mMsecTolerance + 1 >= now) { - ++mSkipped; - } + ++mSkipped; } // no viable candidate @@ -668,9 +683,9 @@ bool Regulator::pullPacket() } PACKETOK : { - if (skipped) { + pullStat->plcOverruns += skipped; + if (skipped && !mLastWasGlitch) { processPacket(true); - pullStat->plcOverruns += skipped; return true; } else processPacket(false); @@ -1081,15 +1096,13 @@ void Regulator::burg(bool glitch) c->mTmpFloatBuf[s] = c->outputNowPacket[s] = ((glitch) ? ((primed) ? c->predictedNowPacket[s] : 0.0) - : ((c->lastWasGlitch) ? (mFadeDown[s] * c->futurePredictedPacket[s] - + mFadeUp[s] * c->realNowPacket[s]) - : c->realNowPacket[s])); + : ((mLastWasGlitch) ? (mFadeDown[s] * c->futurePredictedPacket[s] + + mFadeUp[s] * c->realNowPacket[s]) + : c->realNowPacket[s])); for (int s = 0; s < mPeerFPP; s++) c->mTmpFloatBuf[s] = c->outputNowPacket[s]; - c->lastWasGlitch = glitch; - for (int i = 0; i < mPacketsInThePast - 1; i++) { for (int s = 0; s < mPeerFPP; s++) c->predictedPast[i][s] = c->predictedPast[i + 1][s]; @@ -1105,10 +1118,13 @@ void Regulator::burg(bool glitch) ////////////////////////////////////// } + mLastWasGlitch = glitch; + if ((!(mPcnt % 300)) && (gVerboseFlag)) cout << "PLC avg " << mTime->avg() << " glitches " << mTime->glitches() << " skipped " << (mSkipped - mLastSkipped) << " tolerance " - << (mMsecTolerance - mCurrentHeadroom) << " +" << mCurrentHeadroom << endl; + << (mMsecTolerance - mCurrentHeadroom) << " +" << mCurrentHeadroom + << " latency " << mLastMaxLatency << endl; mPcnt++; // 32 bit is good for days: (/ (* (- (expt 2 32) 1) (/ 32 48000.0)) (* 60 60 24)) } diff --git a/src/Regulator.h b/src/Regulator.h index 2be916f..a4a4b50 100644 --- a/src/Regulator.h +++ b/src/Regulator.h @@ -141,7 +141,6 @@ class Channel int mWptr; int mRing; std::vector mZeros; - bool lastWasGlitch; int mCoeffsSize; int mTailSize; }; @@ -296,6 +295,7 @@ class Regulator : public RingBuffer int mFPPratioDenominator; bool mAuto = false; bool mSkipAutoHeadroom = true; + bool mLastWasGlitch = false; int mSkipped = 0; int mLastSkipped = 0; int mLastGlitches = 0; @@ -304,7 +304,7 @@ class Regulator : public RingBuffer double mStatsMaxLatency = 0; double mStatsMaxPLCdspElapsed = 0; double mCurrentHeadroom = 0; - double mAutoHeadroomStartTime = 6000.0; + double mAutoHeadroomStartTime = 4000.0; double mAutoHeadroom = -1; Time* mTime = nullptr; diff --git a/src/RtAudioInterface.cpp b/src/RtAudioInterface.cpp index 893c6b5..a396642 100644 --- a/src/RtAudioInterface.cpp +++ b/src/RtAudioInterface.cpp @@ -511,6 +511,23 @@ void RtAudioInterface::setup(bool verbose) mStereoToMonoMixerPtr->init(getSampleRate(), bufferFrames); } +//******************************************************************************* +bool RtAudioInterface::sampleRateChanged() const +{ + // TODO: this doesn't seem to work, at least on macos + // sanity check + if (mRtAudioInput.isNull() || mRtAudioInput->getStreamSampleRate() != mInSampleRate) { + return true; + } + if (!mDuplexMode) { + if (mRtAudioOutput.isNull() + || mRtAudioOutput->getStreamSampleRate() != mOutSampleRate) { + return true; + } + } + return false; +} + //******************************************************************************* void RtAudioInterface::printDevices() { diff --git a/src/RtAudioInterface.h b/src/RtAudioInterface.h index 1207eb6..af2d763 100644 --- a/src/RtAudioInterface.h +++ b/src/RtAudioInterface.h @@ -99,6 +99,9 @@ class RtAudioInterface : public AudioInterface /// \brief This has no effect in RtAudio virtual void connectDefaultPorts() {} + /// returns true if the current device sample rates do not match what is expected + bool sampleRateChanged() const; + // returns number of available input audio devices unsigned int getNumInputDevices() const; diff --git a/src/SocketClient.h b/src/SocketClient.h index 281d029..3de974c 100644 --- a/src/SocketClient.h +++ b/src/SocketClient.h @@ -41,7 +41,12 @@ #include // name of the local socket used by JackTrip +// use /tmp/jacktrip.socket on macOS to work around sandboxing +#ifdef Q_OS_MACOS +constexpr const char* JACKTRIP_SOCKET_NAME = "/tmp/jacktrip.socket"; +#else constexpr const char* JACKTRIP_SOCKET_NAME = "JackTrip"; +#endif // SocketClient lists for local socket connections from remote JackTrip processes class SocketClient : public QObject diff --git a/src/SocketServer.cpp b/src/SocketServer.cpp index ed36602..bd32862 100644 --- a/src/SocketServer.cpp +++ b/src/SocketServer.cpp @@ -82,23 +82,30 @@ bool SocketServer::start() void SocketServer::handlePendingConnections() { + const int timeout = 250; while (m_instanceServer->hasPendingConnections()) { QLocalSocket* connectedSocket = m_instanceServer->nextPendingConnection(); + if (connectedSocket == nullptr) { + continue; + } + connectedSocket->setParent(nullptr); + QSharedPointer sharedSocket(connectedSocket); - if (connectedSocket == nullptr || !connectedSocket->waitForConnected()) { - qDebug() << "Socket server: never received connection"; + if (!connectedSocket->waitForConnected(timeout)) { + qDebug() << "Socket server: timed out waiting for connection"; continue; } - if (!connectedSocket->waitForReadyRead() - && connectedSocket->bytesAvailable() <= 0) { - qDebug() << "Socket server: not ready and no bytes available: " - << connectedSocket->errorString(); + // wait for 1 second for ready read, or else give up + bool readyToRead = connectedSocket->waitForReadyRead(timeout); + if (!readyToRead) { + qDebug() << "Socket server: timed out waiting for bytes available"; continue; } if (connectedSocket->bytesAvailable() < (int)sizeof(quint16)) { - qDebug() << "Socket server: ready but no bytes available"; + qDebug() << "Socket server: ready but not enough bytes available (" + << connectedSocket->bytesAvailable() << ")"; continue; } @@ -121,8 +128,6 @@ void SocketServer::handlePendingConnections() cout << "Socket server: received connection for " << handlerName.toStdString() << endl; - connectedSocket->setParent(nullptr); - QSharedPointer sharedSocket(connectedSocket); handleConnection(handlerName, sharedSocket); } } diff --git a/src/UdpHubListener.cpp b/src/UdpHubListener.cpp index e6fd733..e42f6ad 100644 --- a/src/UdpHubListener.cpp +++ b/src/UdpHubListener.cpp @@ -238,7 +238,7 @@ void UdpHubListener::receivedNewConnection() { QSslSocket* clientSocket = static_cast(mTcpServer.nextPendingConnection()); - connect(clientSocket, &QAbstractSocket::readyRead, this, [=] { + connect(clientSocket, &QAbstractSocket::readyRead, this, [this, clientSocket] { receivedClientInfo(clientSocket); }); cout << "JackTrip HUB SERVER: Client Connection Received!" << endl; diff --git a/src/auv2/Info.plist b/src/auv2/Info.plist new file mode 100644 index 0000000..58201c7 --- /dev/null +++ b/src/auv2/Info.plist @@ -0,0 +1,55 @@ + + + + + LSMinimumSystemVersion + 12.0 + CFBundleDevelopmentRegion + en + CFBundleExecutable + JackTrip + CFBundleDisplayName + JackTrip Audio Bridge + CFBundleGetInfoString + JackTrip Audio Bridge + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + BNDL + CFBundleIdentifier + %BUNDLEID% + CFBundleName + %BUNDLENAME% + CFBundleVersion + %VERSION% + CFBundleShortVersionString + %VERSION% + NSHumanReadableCopyright + Copyright © 2024-2025 JackTrip Labs, Inc. + AudioComponents + + + name + JackTrip: JackTrip Audio Bridge + description + Connects audio with the JackTrip App + factoryFunction + JackTripAUFactory + manufacturer + JKTP + type + aufx + subtype + ABv2 + version + %AUVERSION% + tags + + Effect + + sandboxSafe + + + + + diff --git a/src/auv2/JackTripAU.cpp b/src/auv2/JackTripAU.cpp new file mode 100644 index 0000000..62016b9 --- /dev/null +++ b/src/auv2/JackTripAU.cpp @@ -0,0 +1,309 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2024-2025 JackTrip Labs, Inc. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +#include "JackTripAU.h" + +#include +#include + +#include +#include +#include + +using namespace std; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +#pragma mark ____JackTripAU Processing + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +#pragma mark ____JackTripAU + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Standard DSP AudioUnit implementation +AUSDK_COMPONENT_ENTRY(ausdk::AUBaseProcessFactory, JackTripAU) + +// Parameter names +static CFStringRef kSendGain_Name = CFSTR("Send Gain"); +static CFStringRef kOutputMix_Name = CFSTR("Output Mix"); +static CFStringRef kOutputGain_Name = CFSTR("Output Gain"); +static CFStringRef kConnected_Name = CFSTR("Connected"); + +// Parameter ranges +const float kMinGain = 0.0; +const float kMaxGain = 1.0; +const float kDefaultSendGain = 1.0; +const float kDefaultOutputMix = 0.0; +const float kDefaultOutputGain = 1.0; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +#pragma mark ____Construction_Initialization + +JackTripAU::JackTripAU(AudioUnit component) : AUEffectBase(component) +{ + // all the parameters must be set to their initial values here + // + // these calls have the effect both of defining the parameters for the first time + // and assigning their initial values + // + SetParameter(kJackTripAUParam_SendGain, kDefaultSendGain); + SetParameter(kJackTripAUParam_OutputMix, kDefaultOutputMix); + SetParameter(kJackTripAUParam_OutputGain, kDefaultOutputGain); + SetParameter(kJackTripAUParam_Connected, 0); + + updateVolumeMultipliers(true); +} + +JackTripAU::~JackTripAU() +{ + // Cleanup is handled in Cleanup() +} + +OSStatus JackTripAU::Initialize() +{ + OSStatus result = AUEffectBase::Initialize(); + + if (result == noErr) { + // Initialize the audio bridge processor + mProcessor.initialize(GetSampleRate(), GetMaxFramesPerSlice()); + } + + return result; +} + +void JackTripAU::Cleanup() +{ + // Uninitialize the audio bridge processor + mProcessor.uninitialize(); + + AUEffectBase::Cleanup(); +} + +OSStatus JackTripAU::ProcessBufferLists( + [[maybe_unused]] AudioUnitRenderActionFlags& ioActionFlags, + const AudioBufferList& inBuffer, AudioBufferList& outBuffer, UInt32 inFramesToProcess) +{ + // Update connection state from processor + bool connected = GetParameter(kJackTripAUParam_Connected) > 0.5; + if (connected != mProcessor.isEstablished()) { + SetParameter(kJackTripAUParam_Connected, mProcessor.isEstablished() ? 1.0 : 0.0); + // Notify parameter listeners of the change + AudioUnitParameter changedParam = {GetComponentInstance(), + kJackTripAUParam_Connected, + kAudioUnitScope_Global, 0}; + AUParameterSet(nullptr, nullptr, &changedParam, + mProcessor.isEstablished() ? 1.0f : 0.0f, 0); + } + + // Check if bypass is enabled + if (ShouldBypassEffect()) { + // Bypass mode - copy input to output + for (UInt32 ch = 0; ch < outBuffer.mNumberBuffers; ch++) { + if (ch < inBuffer.mNumberBuffers) { + memcpy(outBuffer.mBuffers[ch].mData, inBuffer.mBuffers[ch].mData, + inFramesToProcess * sizeof(Float32)); + } else { + memset(outBuffer.mBuffers[ch].mData, 0, + inFramesToProcess * sizeof(Float32)); + } + } + return noErr; + } + + // Update parameters and calculate volume multipliers + updateVolumeMultipliers(false); + + // Set up input buffers + bool inputSilenceFlags[AudioSocketNumChannels]; + float* inputBuffers[AudioSocketNumChannels]; + for (UInt32 ch = 0; ch < AudioSocketNumChannels; ch++) { + if (ch < inBuffer.mNumberBuffers) { + inputBuffers[ch] = static_cast(inBuffer.mBuffers[ch].mData); + } else { + inputBuffers[ch] = nullptr; + } + inputSilenceFlags[ch] = false; + } + + // Set up output buffers + bool outputSilenceFlags[AudioSocketNumChannels]; + float* outputBuffers[AudioSocketNumChannels]; + for (UInt32 ch = 0; ch < AudioSocketNumChannels; ch++) { + if (ch < outBuffer.mNumberBuffers) { + outputBuffers[ch] = static_cast(outBuffer.mBuffers[ch].mData); + } else { + outputBuffers[ch] = nullptr; + } + } + + // Process through the audio bridge processor + mProcessor.process(inputBuffers, outputBuffers, inputSilenceFlags, outputSilenceFlags, + inFramesToProcess); + + // Handle any remaining output channels by zeroing them + for (UInt32 ch = AudioSocketNumChannels; ch < outBuffer.mNumberBuffers; ch++) { + memset(outBuffer.mBuffers[ch].mData, 0, outBuffer.mBuffers[ch].mDataByteSize); + } + + return noErr; +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +#pragma mark ____Parameters + +OSStatus JackTripAU::GetParameterInfo(AudioUnitScope inScope, + AudioUnitParameterID inParameterID, + AudioUnitParameterInfo& outParameterInfo) +{ + OSStatus result = noErr; + + outParameterInfo.flags = + kAudioUnitParameterFlag_IsWritable + kAudioUnitParameterFlag_IsReadable; + + if (inScope == kAudioUnitScope_Global) { + switch (inParameterID) { + case kJackTripAUParam_SendGain: + AUBase::FillInParameterName(outParameterInfo, kSendGain_Name, false); + outParameterInfo.unit = kAudioUnitParameterUnit_LinearGain; + outParameterInfo.minValue = kMinGain; + outParameterInfo.maxValue = kMaxGain; + outParameterInfo.defaultValue = kDefaultSendGain; + outParameterInfo.flags += kAudioUnitParameterFlag_IsHighResolution; + break; + + case kJackTripAUParam_OutputMix: + AUBase::FillInParameterName(outParameterInfo, kOutputMix_Name, false); + outParameterInfo.unit = kAudioUnitParameterUnit_LinearGain; + outParameterInfo.minValue = kMinGain; + outParameterInfo.maxValue = kMaxGain; + outParameterInfo.defaultValue = kDefaultOutputMix; + outParameterInfo.flags += kAudioUnitParameterFlag_IsHighResolution; + break; + + case kJackTripAUParam_OutputGain: + AUBase::FillInParameterName(outParameterInfo, kOutputGain_Name, false); + outParameterInfo.unit = kAudioUnitParameterUnit_LinearGain; + outParameterInfo.minValue = kMinGain; + outParameterInfo.maxValue = kMaxGain; + outParameterInfo.defaultValue = kDefaultOutputGain; + outParameterInfo.flags += kAudioUnitParameterFlag_IsHighResolution; + break; + + case kJackTripAUParam_Connected: + AUBase::FillInParameterName(outParameterInfo, kConnected_Name, false); + outParameterInfo.unit = kAudioUnitParameterUnit_Boolean; + outParameterInfo.minValue = 0; + outParameterInfo.maxValue = 1; + outParameterInfo.defaultValue = 0; + outParameterInfo.flags += kAudioUnitParameterFlag_IsReadable; + outParameterInfo.flags -= kAudioUnitParameterFlag_IsWritable; + break; + + default: + result = kAudioUnitErr_InvalidParameter; + break; + } + } else { + result = kAudioUnitErr_InvalidParameter; + } + + return result; +} + +OSStatus JackTripAU::GetPropertyInfo(AudioUnitPropertyID inID, AudioUnitScope inScope, + AudioUnitElement inElement, UInt32& outDataSize, + bool& outWritable) +{ + if (inScope == kAudioUnitScope_Global) { + switch (inID) { + case kAudioUnitProperty_SupportedNumChannels: + outDataSize = + sizeof(AUChannelInfo) * 1; // We support exactly one configuration + outWritable = false; + return noErr; + case kAudioUnitProperty_CocoaUI: + outDataSize = sizeof(AudioUnitCocoaViewInfo); + outWritable = false; + return noErr; + } + } + + return AUEffectBase::GetPropertyInfo(inID, inScope, inElement, outDataSize, + outWritable); +} + +OSStatus JackTripAU::GetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope, + AudioUnitElement inElement, void* outData) +{ + if (inScope == kAudioUnitScope_Global) { + switch (inID) { + case kAudioUnitProperty_SupportedNumChannels: { + // We support exactly 2 input channels and 2 output channels + AUChannelInfo* channelInfo = static_cast(outData); + channelInfo[0].inChannels = 2; + channelInfo[0].outChannels = 2; + return noErr; + } + case kAudioUnitProperty_CocoaUI: { + // Return information about our custom Cocoa UI + AudioUnitCocoaViewInfo* viewInfo = + static_cast(outData); + return JackTrip_GetCocoaUI(viewInfo); + } + } + } + + return AUEffectBase::GetProperty(inID, inScope, inElement, outData); +} + +void JackTripAU::updateVolumeMultipliers(bool force) +{ + // Check parameter values + float sendGain = GetParameter(kJackTripAUParam_SendGain); + float outputMix = GetParameter(kJackTripAUParam_OutputMix); + float outputGain = GetParameter(kJackTripAUParam_OutputGain); + + // Since this gets called on every process block, we only update if the parameters + // have changed + if (!force && sendGain == mSendGain && outputMix == mOutputMix + && outputGain == mOutputGain) { + return; + } + + // Calculate volume multipliers and update processor + float outMul = AudioBridgeProcessor::gainToVol(outputGain); + float sendMul = AudioBridgeProcessor::gainToVol(sendGain); + float recvMul = outputMix * outMul; + float passMul = (1.0f - outputMix) * outMul; + + // Update processor parameters + mProcessor.setSendMul(sendMul); + mProcessor.setRecvMul(recvMul); + mProcessor.setPassMul(passMul); +} diff --git a/src/auv2/JackTripAU.h b/src/auv2/JackTripAU.h new file mode 100644 index 0000000..8e0a7b0 --- /dev/null +++ b/src/auv2/JackTripAU.h @@ -0,0 +1,93 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2024-2025 JackTrip Labs, Inc. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +// based on Filter sample code from https://github.com/apple/AudioUnit-Examples + +#include +#include + +#include + +#include "../AudioBridgeProcessor.h" + +// Forward declaration for Cocoa UI support +// AudioUnitCocoaViewInfo is defined in AudioUnitProperties.h +extern "C" OSStatus JackTrip_GetCocoaUI(AudioUnitCocoaViewInfo* viewInfo); + +#ifndef __JackTripAU_h__ +#define __JackTripAU_h__ + +enum JackTripAUParams { + kJackTripAUParam_SendGain = 0, + kJackTripAUParam_OutputMix = 1, + kJackTripAUParam_OutputGain = 2, + kJackTripAUParam_Connected = 3, +}; + +class JackTripAU : public ausdk::AUEffectBase +{ + public: + JackTripAU(AudioUnit component); + ~JackTripAU(); + + OSStatus Initialize() override; + void Cleanup() override; + + OSStatus ProcessBufferLists(AudioUnitRenderActionFlags& ioActionFlags, + const AudioBufferList& inBuffer, + AudioBufferList& outBuffer, + UInt32 inFramesToProcess) override; + + OSStatus GetParameterInfo(AudioUnitScope inScope, AudioUnitParameterID inParameterID, + AudioUnitParameterInfo& outParameterInfo) override; + + OSStatus GetPropertyInfo(AudioUnitPropertyID inID, AudioUnitScope inScope, + AudioUnitElement inElement, UInt32& outDataSize, + bool& outWritable) override; + + OSStatus GetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope, + AudioUnitElement inElement, void* outData) override; + + Float64 GetLatency() override { return 0.0; } + + private: + // Updates volume multipliers in processor + void updateVolumeMultipliers(bool force); + + // Audio bridge processor + AudioBridgeProcessor mProcessor; + + // Cache values of current parameters + float mSendGain = 1.f; + float mOutputMix = 0; + float mOutputGain = 1.f; +}; + +#endif \ No newline at end of file diff --git a/src/auv2/JackTripAUCocoaUI.mm b/src/auv2/JackTripAUCocoaUI.mm new file mode 100644 index 0000000..cdd9df2 --- /dev/null +++ b/src/auv2/JackTripAUCocoaUI.mm @@ -0,0 +1,64 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2024-2025 JackTrip Labs, Inc. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +#import +#import +#import +#import + +// Import the view factory interface +@interface JackTripAUViewFactory : NSObject +@end + +extern "C" { + OSStatus JackTrip_GetCocoaUI(AudioUnitCocoaViewInfo* viewInfo) { + if (viewInfo == NULL) { + return kAudioUnitErr_InvalidParameter; + } + + // Get the bundle containing this AudioUnit + NSBundle* bundle = [NSBundle bundleForClass:[JackTripAUViewFactory class]]; + if (bundle == nil) { + // Fallback to main bundle + bundle = [NSBundle mainBundle]; + if (bundle == nil) { + return kAudioUnitErr_InvalidParameter; + } + } + + // Set the bundle location + viewInfo->mCocoaAUViewBundleLocation = (__bridge CFURLRef)[bundle bundleURL]; + + // Set the view factory class name + viewInfo->mCocoaAUViewClass[0] = (__bridge CFStringRef)@"JackTripAUViewFactory"; + + return noErr; + } +} \ No newline at end of file diff --git a/src/auv2/JackTripAUView.h b/src/auv2/JackTripAUView.h new file mode 100644 index 0000000..e72bfd8 --- /dev/null +++ b/src/auv2/JackTripAUView.h @@ -0,0 +1,111 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2024-2025 JackTrip Labs, Inc. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +#import +#import +#import +#import + +#ifndef __JackTripAUView_h__ +#define __JackTripAUView_h__ + +@class JackTripKnobView; +@class JackTripLEDView; + +@interface JackTripAUView : NSView { + AudioUnit mAU; + AUEventListenerRef mParameterListener; + NSTimer* mUpdateTimer; + + // UI Elements + NSImageView* mBackgroundView; + JackTripKnobView* mSendKnob; + JackTripKnobView* mOutputMixKnob; + JackTripKnobView* mOutputGainKnob; + JackTripLEDView* mConnectionLED; + + // Labels + NSTextField* mSendLabel; + NSTextField* mOutputMixLabel; + NSTextField* mOutputGainLabel; + NSTextField* mToJackTripLabel; + NSTextField* mFromJackTripLabel; + NSTextField* mPassThroughLabel; + + // Resources + NSImage* mBackgroundImage; + NSImage* mKnobImage; + NSImage* mLEDImage; +} + +- (id)initWithFrame:(NSRect)frameRect audioUnit:(AudioUnit)inAU; +- (void)dealloc; +- (void)setAudioUnit:(AudioUnit)inAU; +- (void)parameterChanged:(AudioUnitParameterID)parameterID; + +@end + +// Custom knob control +@interface JackTripKnobView : NSView { + AudioUnit mAU; + AudioUnitParameterID mParameterID; + NSImage* mKnobImage; + float mValue; + float mMinValue; + float mMaxValue; + int mFrameCount; + NSPoint mLastMousePoint; + BOOL mIsTracking; +} + +- (id)initWithFrame:(NSRect)frameRect + audioUnit:(AudioUnit)inAU + parameterID:(AudioUnitParameterID)parameterID + knobImage:(NSImage*)knobImage + frameCount:(int)frameCount; +- (void)setValue:(float)value; +- (float)getValue; +- (void)setParameterRange:(float)minValue maxValue:(float)maxValue; +- (void)setAudioUnit:(AudioUnit)inAU; + +@end + +// Custom LED indicator +@interface JackTripLEDView : NSView { + NSImage* mLEDImage; + BOOL mIsConnected; +} + +- (id)initWithFrame:(NSRect)frameRect LEDImage:(NSImage*)ledImage; +- (void)setConnected:(BOOL)connected; + +@end + +#endif \ No newline at end of file diff --git a/src/auv2/JackTripAUView.mm b/src/auv2/JackTripAUView.mm new file mode 100644 index 0000000..73b2596 --- /dev/null +++ b/src/auv2/JackTripAUView.mm @@ -0,0 +1,418 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2024-2025 JackTrip Labs, Inc. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +#import "JackTripAUView.h" +#include "JackTripAU.h" + +// Parameter listener callback +static void ParameterListenerDispatcher(void* inRefCon, void* /* inObject */, + const AudioUnitParameter* inParameter, + Float32 /* inValue */) { + JackTripAUView* view = (__bridge JackTripAUView*)inRefCon; + [view parameterChanged:inParameter->mParameterID]; +} + +@implementation JackTripAUView + +- (id)initWithFrame:(NSRect)frameRect audioUnit:(AudioUnit)inAU { + self = [super initWithFrame:frameRect]; + if (self) { + mAU = inAU; + mParameterListener = nullptr; + + // Load images from the component's bundle + NSBundle* componentBundle = [NSBundle bundleForClass:[self class]]; + + // Try to load the high-resolution background first, fall back to regular version + NSString* backgroundPath = [componentBundle pathForResource:@"background_2x" ofType:@"png"]; + if (!backgroundPath) { + backgroundPath = [componentBundle pathForResource:@"background" ofType:@"png"]; + } + mBackgroundImage = backgroundPath ? [[NSImage alloc] initWithContentsOfFile:backgroundPath] : nil; + + NSString* knobPath = [componentBundle pathForResource:@"Sercan_Moog_Knob" ofType:@"png"]; + mKnobImage = knobPath ? [[NSImage alloc] initWithContentsOfFile:knobPath] : nil; + + NSString* ledPath = [componentBundle pathForResource:@"Dual_LED" ofType:@"png"]; + mLEDImage = ledPath ? [[NSImage alloc] initWithContentsOfFile:ledPath] : nil; + + // Debug logging to help troubleshoot image loading + NSLog(@"JackTrip AUv2: Bundle path = %@", [componentBundle bundlePath]); + NSLog(@"JackTrip AUv2: Background image %@ (path: %@)", mBackgroundImage ? @"loaded" : @"FAILED", backgroundPath); + NSLog(@"JackTrip AUv2: Knob image %@ (path: %@)", mKnobImage ? @"loaded" : @"FAILED", knobPath); + NSLog(@"JackTrip AUv2: LED image %@ (path: %@)", mLEDImage ? @"loaded" : @"FAILED", ledPath); + + [self setupUI]; + [self setupParameterListener]; + + // Initialize knob values from current parameter values + [self updateAllParameterValues]; + + // Set up a timer to periodically check connection state + mUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 + target:self + selector:@selector(updateConnectionStatus:) + userInfo:nil + repeats:YES]; + } + return self; +} + +- (void)dealloc { + if (mParameterListener) { + AUListenerDispose(mParameterListener); + } + + // Stop and release timer + if (mUpdateTimer) { + [mUpdateTimer invalidate]; + mUpdateTimer = nil; + } + + // Release images + [mBackgroundImage release]; + [mKnobImage release]; + [mLEDImage release]; + + [super dealloc]; +} + +- (void)setupUI { + // Set up background + mBackgroundView = [[NSImageView alloc] initWithFrame:self.bounds]; + [mBackgroundView setImage:mBackgroundImage]; + [mBackgroundView setImageScaling:NSImageScaleProportionallyUpOrDown]; + [self addSubview:mBackgroundView]; + + // Connection LED (top right) + NSRect ledFrame = NSMakeRect(335, 200-70, 62, 62); // Adjust Y for Cocoa coordinates + mConnectionLED = [[JackTripLEDView alloc] initWithFrame:ledFrame LEDImage:mLEDImage]; + [self addSubview:mConnectionLED]; + + // Send knob and labels + NSRect sendKnobFrame = NSMakeRect(70, 200-172, 72, 72); // Adjust Y for Cocoa coordinates + mSendKnob = [[JackTripKnobView alloc] initWithFrame:sendKnobFrame + audioUnit:mAU + parameterID:kJackTripAUParam_SendGain + knobImage:mKnobImage + frameCount:120]; + [mSendKnob setParameterRange:0.0 maxValue:1.0]; + [self addSubview:mSendKnob]; + + // Send labels + mSendLabel = [self createLabel:NSMakeRect(75, 200-100, 60, 20) text:@"Send"]; + [self addSubview:mSendLabel]; + + mToJackTripLabel = [self createSmallLabel:NSMakeRect(50, 200-190, 110, 15) text:@"To JackTrip"]; + [self addSubview:mToJackTripLabel]; + + // Output Mix knob and labels + NSRect mixKnobFrame = NSMakeRect(250, 200-172, 72, 72); // Adjust Y for Cocoa coordinates + mOutputMixKnob = [[JackTripKnobView alloc] initWithFrame:mixKnobFrame + audioUnit:mAU + parameterID:kJackTripAUParam_OutputMix + knobImage:mKnobImage + frameCount:120]; + [mOutputMixKnob setParameterRange:0.0 maxValue:1.0]; + [self addSubview:mOutputMixKnob]; + + // Output Mix labels + mOutputMixLabel = [self createLabel:NSMakeRect(235, 200-100, 100, 20) text:@"Output Mix"]; + [self addSubview:mOutputMixLabel]; + + mPassThroughLabel = [self createSmallLabel:NSMakeRect(195, 200-190, 90, 15) text:@"Pass-Through"]; + [self addSubview:mPassThroughLabel]; + + mFromJackTripLabel = [self createSmallLabel:NSMakeRect(285, 200-190, 90, 15) text:@"From JackTrip"]; + [self addSubview:mFromJackTripLabel]; + + // For now, we'll skip the Output Gain knob since it's not in the original AUv2 parameters + // but we can add it later if needed +} + +- (NSTextField*)createLabel:(NSRect)frame text:(NSString*)text { + NSTextField* label = [[NSTextField alloc] initWithFrame:frame]; + [label setStringValue:text]; + [label setBezeled:NO]; + [label setDrawsBackground:NO]; + [label setEditable:NO]; + [label setSelectable:NO]; + [label setTextColor:[NSColor whiteColor]]; + [label setAlignment:NSTextAlignmentCenter]; + [label setFont:[NSFont systemFontOfSize:14 weight:NSFontWeightMedium]]; + [[label cell] setLineBreakMode:NSLineBreakByClipping]; + return label; +} + +- (NSTextField*)createSmallLabel:(NSRect)frame text:(NSString*)text { + NSTextField* label = [[NSTextField alloc] initWithFrame:frame]; + [label setStringValue:text]; + [label setBezeled:NO]; + [label setDrawsBackground:NO]; + [label setEditable:NO]; + [label setSelectable:NO]; + [label setTextColor:[NSColor whiteColor]]; + [label setAlignment:NSTextAlignmentCenter]; + [label setFont:[NSFont systemFontOfSize:10]]; + [[label cell] setLineBreakMode:NSLineBreakByClipping]; + return label; +} + +- (void)setupParameterListener { + OSStatus result = AUListenerCreate(ParameterListenerDispatcher, (__bridge void*)self, + CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, + 0.05, &mParameterListener); + if (result == noErr) { + AudioUnitParameter parameter; + parameter.mAudioUnit = mAU; + parameter.mScope = kAudioUnitScope_Global; + parameter.mElement = 0; + + // Listen to parameter changes + parameter.mParameterID = kJackTripAUParam_SendGain; + AUListenerAddParameter(mParameterListener, nullptr, ¶meter); + + parameter.mParameterID = kJackTripAUParam_OutputMix; + AUListenerAddParameter(mParameterListener, nullptr, ¶meter); + + parameter.mParameterID = kJackTripAUParam_OutputGain; + AUListenerAddParameter(mParameterListener, nullptr, ¶meter); + + parameter.mParameterID = kJackTripAUParam_Connected; + AUListenerAddParameter(mParameterListener, nullptr, ¶meter); + } +} + +- (void)setAudioUnit:(AudioUnit)inAU { + mAU = inAU; + [mSendKnob setAudioUnit:inAU]; + [mOutputMixKnob setAudioUnit:inAU]; + + // Update initial values + [self parameterChanged:kJackTripAUParam_SendGain]; + [self parameterChanged:kJackTripAUParam_OutputMix]; + [self parameterChanged:kJackTripAUParam_Connected]; +} + +- (void)updateAllParameterValues { + if (!mAU) return; + + [self parameterChanged:kJackTripAUParam_SendGain]; + [self parameterChanged:kJackTripAUParam_OutputMix]; + [self parameterChanged:kJackTripAUParam_Connected]; +} + +- (void)updateConnectionStatus:(NSTimer*)timer { + // Periodically check connection status in case parameter listener misses updates + [self parameterChanged:kJackTripAUParam_Connected]; +} + +- (void)parameterChanged:(AudioUnitParameterID)parameterID { + if (!mAU) return; + + Float32 value; + OSStatus result = AudioUnitGetParameter(mAU, parameterID, kAudioUnitScope_Global, 0, &value); + if (result != noErr) { + NSLog(@"JackTrip AUv2: Failed to get parameter %d, error %d", (int)parameterID, (int)result); + return; + } + + switch (parameterID) { + case kJackTripAUParam_SendGain: + [mSendKnob setValue:value]; + NSLog(@"JackTrip AUv2: Send gain updated to %f", value); + break; + case kJackTripAUParam_OutputMix: + [mOutputMixKnob setValue:value]; + NSLog(@"JackTrip AUv2: Output mix updated to %f", value); + break; + case kJackTripAUParam_Connected: + [mConnectionLED setConnected:(value > 0.5)]; + NSLog(@"JackTrip AUv2: Connected state updated to %s", (value > 0.5) ? "true" : "false"); + break; + default: + break; + } +} + +@end + +#pragma mark - JackTripKnobView Implementation + +@implementation JackTripKnobView + +- (id)initWithFrame:(NSRect)frameRect + audioUnit:(AudioUnit)inAU + parameterID:(AudioUnitParameterID)parameterID + knobImage:(NSImage*)knobImage + frameCount:(int)frameCount { + self = [super initWithFrame:frameRect]; + if (self) { + mAU = inAU; + mParameterID = parameterID; + mKnobImage = knobImage; + mFrameCount = frameCount; + mMinValue = 0.0; + mMaxValue = 1.0; + mIsTracking = NO; + + // Get the current parameter value from the AudioUnit + if (mAU) { + Float32 currentValue; + OSStatus result = AudioUnitGetParameter(mAU, mParameterID, kAudioUnitScope_Global, 0, ¤tValue); + mValue = (result == noErr) ? currentValue : 0.5; + } else { + mValue = 0.5; // fallback + } + } + return self; +} + +- (void)setAudioUnit:(AudioUnit)inAU { + mAU = inAU; +} + +- (void)setValue:(float)value { + mValue = fmax(mMinValue, fmin(mMaxValue, value)); + [self setNeedsDisplay:YES]; +} + +- (float)getValue { + return mValue; +} + +- (void)setParameterRange:(float)minValue maxValue:(float)maxValue { + mMinValue = minValue; + mMaxValue = maxValue; +} + +- (void)drawRect:(NSRect)dirtyRect { + if (!mKnobImage) return; + + NSRect bounds = self.bounds; + + // Calculate which frame to show based on the current value + float normalizedValue = (mValue - mMinValue) / (mMaxValue - mMinValue); + // Invert the frame index so higher values show later frames (clockwise rotation) + int frameIndex = (int)((1.0f - normalizedValue) * (mFrameCount - 1)); + frameIndex = fmax(0, fmin(mFrameCount - 1, frameIndex)); + + // Debug output (remove after testing) + static int debugCount = 0; + if (debugCount < 5) { // Only log first few draws to avoid spam + NSLog(@"JackTrip Knob %d: value=%f, normalized=%f, frame=%d/%d", + (int)mParameterID, mValue, normalizedValue, frameIndex, mFrameCount-1); + debugCount++; + } + + // Calculate the source rect for the current frame + // The knob image contains 120 frames arranged vertically + NSSize imageSize = [mKnobImage size]; + float frameHeight = imageSize.height / mFrameCount; + NSRect sourceRect = NSMakeRect(0, frameIndex * frameHeight, imageSize.width, frameHeight); + + // Draw the appropriate frame + [mKnobImage drawInRect:bounds fromRect:sourceRect operation:NSCompositingOperationSourceOver fraction:1.0]; +} + +- (void)mouseDown:(NSEvent*)event { + mIsTracking = YES; + mLastMousePoint = [self convertPoint:[event locationInWindow] fromView:nil]; +} + +- (void)mouseDragged:(NSEvent*)event { + if (!mIsTracking) return; + + NSPoint currentPoint = [self convertPoint:[event locationInWindow] fromView:nil]; + float deltaY = currentPoint.y - mLastMousePoint.y; + + // Adjust sensitivity + float sensitivity = 0.005; + float newValue = mValue + (deltaY * sensitivity); + + [self setValue:newValue]; + + // Update AudioUnit parameter + if (mAU) { + AudioUnitSetParameter(mAU, mParameterID, kAudioUnitScope_Global, 0, mValue, 0); + } + + mLastMousePoint = currentPoint; +} + +- (void)mouseUp:(NSEvent*)event { + mIsTracking = NO; +} + +@end + +#pragma mark - JackTripLEDView Implementation + +@implementation JackTripLEDView + +- (id)initWithFrame:(NSRect)frameRect LEDImage:(NSImage*)ledImage { + self = [super initWithFrame:frameRect]; + if (self) { + mLEDImage = ledImage; + mIsConnected = NO; + } + return self; +} + +- (void)setConnected:(BOOL)connected { + if (mIsConnected != connected) { + mIsConnected = connected; + [self setNeedsDisplay:YES]; + } +} + +- (void)drawRect:(NSRect)dirtyRect { + if (!mLEDImage) return; + + NSRect bounds = self.bounds; + + // The LED image contains both red and green states + // Use the appropriate half based on connection state + NSSize imageSize = [mLEDImage size]; + float halfHeight = imageSize.height / 2; + NSRect sourceRect; + + if (mIsConnected) { + // Use bottom half for green (connected) + sourceRect = NSMakeRect(0, 0, imageSize.width, halfHeight); + } else { + // Use top half for red (disconnected) + sourceRect = NSMakeRect(0, halfHeight, imageSize.width, halfHeight); + } + + [mLEDImage drawInRect:bounds fromRect:sourceRect operation:NSCompositingOperationSourceOver fraction:1.0]; +} + +@end \ No newline at end of file diff --git a/src/auv2/JackTripAUViewFactory.mm b/src/auv2/JackTripAUViewFactory.mm new file mode 100644 index 0000000..a8d8d16 --- /dev/null +++ b/src/auv2/JackTripAUViewFactory.mm @@ -0,0 +1,74 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2024-2025 JackTrip Labs, Inc. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +#import +#import +#import "JackTripAUView.h" + +@interface JackTripAUViewFactory : NSObject +{ +} + +@end + +// Export the class for external access +extern "C" { + void* JackTripAUViewFactoryClass() { + return [JackTripAUViewFactory class]; + } +} + +// Make sure the class is available at runtime +__attribute__((constructor)) +static void registerViewFactory() { + // This ensures the class is loaded and available + [JackTripAUViewFactory class]; +} + +@implementation JackTripAUViewFactory + +- (unsigned)interfaceVersion { + return 0; +} + +- (NSString *)description { + return @"JackTrip Audio Unit Custom View"; +} + +- (NSView *)uiViewForAudioUnit:(AudioUnit)inAudioUnit withSize:(NSSize)inPreferredSize { + // Create our custom view with the preferred size + NSRect frame = NSMakeRect(0, 0, 400, 200); // Fixed size matching VST3 UI + + JackTripAUView* view = [[JackTripAUView alloc] initWithFrame:frame audioUnit:inAudioUnit]; + + return [view autorelease]; +} + +@end \ No newline at end of file diff --git a/src/auv2/PkgInfo b/src/auv2/PkgInfo new file mode 100644 index 0000000..19a9cf6 --- /dev/null +++ b/src/auv2/PkgInfo @@ -0,0 +1 @@ +BNDL???? \ No newline at end of file diff --git a/src/auv2/README.md b/src/auv2/README.md new file mode 100644 index 0000000..5579862 --- /dev/null +++ b/src/auv2/README.md @@ -0,0 +1,58 @@ +# JackTrip Audio Unit Plugin (v2) + +This directory contains the Audio Unit (AUv2) implementation of the JackTrip Audio Bridge plugin for macOS. + +## Overview + +The JackTrip Audio Bridge plugin uses the AudioBridgeProcessor class to exchange audio with the JackTrip application. + +## Features + +- **Audio Processing**: Stereo audio input/output with configurable gain controls +- **Network Audio**: Exchange audio with remote JackTrip instances via local socket +- **Parameter Control**: Send gain, output mix, output gain, bypass, and connection status +- **Real-time Audio**: Low-latency audio processing suitable for live performance +- **Interface**: Native Cocoa UI with the same controls as the VST3 version + +## Building + +The AU plugin is built automatically when building JackTrip with static Qt on macOS: + +```bash +meson setup -Ddefault_library=static -Dnogui=true --buildtype release buildstatic +meson compile -C buildstatic +``` + +Use the `macos/assemble_app.sh` to build the installation bundles. + +## Usage + +1. Launch JackTrip in your desired mode +2. Insert the "JackTrip Audio Bridge" AU plugin in your DAW +3. Configure the plugin parameters: + - **Send Gain**: Controls level of audio sent to remote + - **Output Mix**: Blends received audio (0%) with input passthrough (100%) + - **Output Gain**: Master output level + - **Bypass**: Bypasses all processing + - **Connected**: Shows connection status (read-only) + +The plugin will automatically attempt to connect to the JackTrip application via local socket. + +## Requirements + +- macOS 12 or later +- Audio Unit compatible host application +- Host application must have network access via entitlements +- JackTrip application running on the same machine + +## References + +* [Developer Guide for AUv2](https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioUnitProgrammingGuide/AudioUnitDevelopmentFundamentals/AudioUnitDevelopmentFundamentals.html) +* [Audio Unit v2 C API](https://developer.apple.com/documentation/audiotoolbox/audio-unit-v2-c-api) +* [Building AudioUnits with modern Mac OSX](https://teragonaudio.com/article/Building-AudioUnits-with-modern-Mac-OSX.html) +* [Audio Plugin Dev Notes](https://gist.github.com/olilarkin/8f378d212b0a59944d84f9f47061d70f?utm_source=chatgpt.com) + +## License + +Copyright (c) 2024-2025 JackTrip Labs, Inc. +Licensed under the MIT License. \ No newline at end of file diff --git a/src/auv2/meson.build b/src/auv2/meson.build new file mode 100644 index 0000000..fa0d119 --- /dev/null +++ b/src/auv2/meson.build @@ -0,0 +1,53 @@ +# JackTrip Audio Unit Plugin Build Configuration + +# Audio Unit sources +auv2_sources = [ + 'JackTripAU.cpp', + 'JackTripAUView.mm', + 'JackTripAUViewFactory.mm', + 'JackTripAUCocoaUI.mm' +] + +# Required AudioSocket sources (shared with VST3) +audio_socket_moc_h = ['../AudioSocket.h', '../SocketClient.h', '../ProcessPlugin.h'] +audio_socket_sources = qt.compile_moc(headers: audio_socket_moc_h, extra_args: defines) +audio_socket_sources += [ + '../AudioSocket.cpp', + '../AudioBridgeProcessor.cpp', + '../SocketClient.cpp', + '../ProcessPlugin.cpp', + '../jacktrip_globals.cpp' +] + +# Apple frameworks required for Audio Units +auv2_frameworks = dependency('appleframeworks', modules: [ + 'Foundation', + 'AudioToolbox', + 'AudioUnit', + 'CoreServices', + 'Cocoa' +]) + +# Dependencies +auv2_deps = [qt_core_deps, static_deps, auv2_frameworks, audiounitsdk_dep] + +# Add libsamplerate if available +if found_libsamplerate + auv2_deps += libsamplerate_dep +endif + +# Include directories +auv2_incdirs = include_directories('.', '../') + +# Build the Audio Unit bundle +jacktrip_auv2 = shared_module('JackTrip', + auv2_sources, audio_socket_sources, + name_prefix: '', + name_suffix: 'auv2', + dependencies: auv2_deps, + include_directories: auv2_incdirs, + link_args: static_link_args, + cpp_args: defines +) + +message('JackTrip Audio Unit v2 will be built') diff --git a/src/auv3/Info.plist b/src/auv3/Info.plist new file mode 100644 index 0000000..eb1181e --- /dev/null +++ b/src/auv3/Info.plist @@ -0,0 +1,72 @@ + + + + + LSMinimumSystemVersion + 12.0 + CFBundleDevelopmentRegion + en + CFBundleExecutable + JackTrip + CFBundleDisplayName + JackTrip Audio Bridge + CFBundleGetInfoString + JackTrip Audio Bridge + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + XPC! + NSExtensionPointVersion + 1.0 + CFBundleIdentifier + %BUNDLEID% + CFBundleName + %BUNDLENAME% + CFBundleVersion + %VERSION% + CFBundleShortVersionString + %VERSION% + NSHumanReadableCopyright + Copyright © 2024-2025 JackTrip Labs, Inc. + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + NSExtension + + NSExtensionAttributes + + AudioComponents + + + name + JackTrip: JackTrip Audio Bridge + description + Connects audio with the JackTrip App + factoryFunction + JackTripAUEntry + manufacturer + JKTP + type + aufx + subtype + ABv3 + version + %AUVERSION% + tags + + Effect + + sandboxSafe + + + + + NSExtensionPointIdentifier + com.apple.AudioUnit-UI + NSExtensionPrincipalClass + JackTripAU + + + diff --git a/src/auv3/JackTripAU.h b/src/auv3/JackTripAU.h new file mode 100644 index 0000000..0ed82e1 --- /dev/null +++ b/src/auv3/JackTripAU.h @@ -0,0 +1,55 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2024-2025 JackTrip Labs, Inc. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +#pragma once + +#import +#import +#import + +// Parameter addresses - matching AUv2 parameter structure +typedef NS_ENUM(AUParameterAddress, JackTripAUParameterAddress) { + kJackTripAUParam_SendGain = 0, + kJackTripAUParam_OutputMix = 1, + kJackTripAUParam_OutputGain = 2, + kJackTripAUParam_Connected = 3, +}; + +@interface JackTripAU : AUAudioUnit + +@property (nonatomic, readonly) AUAudioUnitBus *inputBus; +@property (nonatomic, readonly) AUAudioUnitBus *outputBus; +@property (nonatomic, readonly) AUAudioUnitBusArray *inputBusArray; +@property (nonatomic, readonly) AUAudioUnitBusArray *outputBusArray; + +// Internal render block +@property (nonatomic, copy) AUInternalRenderBlock internalRenderBlock; + +@end \ No newline at end of file diff --git a/src/auv3/JackTripAU.mm b/src/auv3/JackTripAU.mm new file mode 100644 index 0000000..a3226c6 --- /dev/null +++ b/src/auv3/JackTripAU.mm @@ -0,0 +1,434 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2024-2025 JackTrip Labs, Inc. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +#import "JackTripAU.h" +#import "JackTripAUViewController.h" +#import +#import + +// Import C++ headers +#include "../AudioBridgeProcessor.h" +#include +#include +#include + +// Parameter ranges - matching AUv2 +static const float kMinGain = 0.0f; +static const float kMaxGain = 1.0f; +static const float kDefaultSendGain = 1.0f; +static const float kDefaultOutputMix = 0.0f; +static const float kDefaultOutputGain = 1.0f; + +// C++ implementation class +class JackTripAUImpl { +public: + JackTripAUImpl() { + // Initialize parameters to defaults + mSendGain = kDefaultSendGain; + mOutputMix = kDefaultOutputMix; + mOutputGain = kDefaultOutputGain; + mConnected = false; + + updateVolumeMultipliers(); + } + + ~JackTripAUImpl() { + uninitialize(); + } + + bool initialize(double sampleRate, UInt32 maxFramesToRender) { + mSampleRate = sampleRate; + mMaxFramesToRender = maxFramesToRender; + + // Initialize the audio bridge processor + mProcessor.initialize(static_cast(sampleRate), + static_cast(maxFramesToRender)); + + mInitialized = true; + return true; + } + + void uninitialize() { + if (mInitialized) { + mProcessor.uninitialize(); + mInitialized = false; + } + } + + void setParameter(AUParameterAddress address, float value) { + switch (address) { + case kJackTripAUParam_SendGain: + mSendGain = std::clamp(value, kMinGain, kMaxGain); + break; + case kJackTripAUParam_OutputMix: + mOutputMix = std::clamp(value, kMinGain, kMaxGain); + break; + case kJackTripAUParam_OutputGain: + mOutputGain = std::clamp(value, kMinGain, kMaxGain); + break; + case kJackTripAUParam_Connected: + // This is read-only, updated internally + break; + } + updateVolumeMultipliers(); + } + + float getParameter(AUParameterAddress address) { + switch (address) { + case kJackTripAUParam_SendGain: + return mSendGain; + case kJackTripAUParam_OutputMix: + return mOutputMix; + case kJackTripAUParam_OutputGain: + return mOutputGain; + case kJackTripAUParam_Connected: + return mConnected ? 1.0f : 0.0f; + default: + return 0.0f; + } + } + + void processAudio(AudioBufferList* inBufferList, + AudioBufferList* outBufferList, + UInt32 frameCount) { + if (!mInitialized) { + // Zero output if not initialized + for (UInt32 i = 0; i < outBufferList->mNumberBuffers; ++i) { + memset(outBufferList->mBuffers[i].mData, 0, + outBufferList->mBuffers[i].mDataByteSize); + } + return; + } + + // Update connection state + bool currentlyConnected = mProcessor.isConnected(); + if (currentlyConnected != mConnected) { + mConnected = currentlyConnected; + } + + // Set up input buffers + bool inputSilenceFlags[AudioSocketNumChannels]; + float* inputBuffers[AudioSocketNumChannels]; + for (UInt32 ch = 0; ch < AudioSocketNumChannels; ch++) { + if (ch < inBufferList->mNumberBuffers) { + inputBuffers[ch] = static_cast(inBufferList->mBuffers[ch].mData); + } else { + inputBuffers[ch] = nullptr; + } + inputSilenceFlags[ch] = false; + } + + // Set up output buffers + bool outputSilenceFlags[AudioSocketNumChannels]; + float* outputBuffers[AudioSocketNumChannels]; + for (UInt32 ch = 0; ch < AudioSocketNumChannels; ch++) { + if (ch < outBufferList->mNumberBuffers) { + outputBuffers[ch] = static_cast(outBufferList->mBuffers[ch].mData); + } else { + outputBuffers[ch] = nullptr; + } + } + + // Process through the audio bridge processor + mProcessor.process(inputBuffers, outputBuffers, + inputSilenceFlags, outputSilenceFlags, + static_cast(frameCount)); + + // Handle any remaining output channels by zeroing them + for (UInt32 ch = AudioSocketNumChannels; ch < outBufferList->mNumberBuffers; ch++) { + memset(outBufferList->mBuffers[ch].mData, 0, + outBufferList->mBuffers[ch].mDataByteSize); + } + } + + bool isConnected() const { + return mConnected; + } + +private: + void updateVolumeMultipliers(void) { + // Calculate volume multipliers from parameters + float outMul = AudioBridgeProcessor::gainToVol(mOutputGain); + float sendMul = AudioBridgeProcessor::gainToVol(mSendGain); + float recvMul = mOutputMix * outMul; + float passMul = (1.0f - mOutputMix) * outMul; + + // Update processor parameters + mProcessor.setSendMul(sendMul); + mProcessor.setRecvMul(recvMul); + mProcessor.setPassMul(passMul); + } + + // Audio bridge processor + AudioBridgeProcessor mProcessor; + + // Parameters + float mSendGain; + float mOutputMix; + float mOutputGain; + bool mConnected; + + // State + double mSampleRate = 44100.0; + UInt32 mMaxFramesToRender = 512; + bool mInitialized = false; +}; + +@interface JackTripAU () { + // C++ implementation + std::unique_ptr mImpl; +} + +@property (nonatomic, readwrite) AUAudioUnitBus *inputBus; +@property (nonatomic, readwrite) AUAudioUnitBus *outputBus; +@property (nonatomic, readwrite) AUAudioUnitBusArray *inputBusArray; +@property (nonatomic, readwrite) AUAudioUnitBusArray *outputBusArray; + +@end + +@implementation JackTripAU + +- (instancetype)initWithComponentDescription:(AudioComponentDescription)componentDescription + options:(AudioComponentInstantiationOptions)options + error:(NSError **)outError { + self = [super initWithComponentDescription:componentDescription options:options error:outError]; + if (self == nil) { + return nil; + } + + // Create C++ implementation + mImpl = std::make_unique(); + + // Create audio format (stereo 32-bit float) + AVAudioFormat *defaultFormat = [[AVAudioFormat alloc] initStandardFormatWithSampleRate:44100.0 + channels:2]; + + // Create input and output buses + _inputBus = [[AUAudioUnitBus alloc] initWithFormat:defaultFormat error:nil]; + _outputBus = [[AUAudioUnitBus alloc] initWithFormat:defaultFormat error:nil]; + + // Create bus arrays + _inputBusArray = [[AUAudioUnitBusArray alloc] initWithAudioUnit:self + busType:AUAudioUnitBusTypeInput + busses:@[_inputBus]]; + _outputBusArray = [[AUAudioUnitBusArray alloc] initWithAudioUnit:self + busType:AUAudioUnitBusTypeOutput + busses:@[_outputBus]]; + + // Create parameters + [self createParameters]; + + // Set maximum frames to render + self.maximumFramesToRender = 512; + + return self; +} + +- (void)createParameters { + // Create parameter objects + AUParameter *sendGainParam = [AUParameterTree createParameterWithIdentifier:@"sendGain" + name:@"Send Gain" + address:kJackTripAUParam_SendGain + min:kMinGain + max:kMaxGain + unit:kAudioUnitParameterUnit_LinearGain + unitName:nil + flags:kAudioUnitParameterFlag_IsWritable | + kAudioUnitParameterFlag_IsReadable | + kAudioUnitParameterFlag_IsHighResolution + valueStrings:nil + dependentParameters:nil]; + + AUParameter *outputMixParam = [AUParameterTree createParameterWithIdentifier:@"outputMix" + name:@"Output Mix" + address:kJackTripAUParam_OutputMix + min:kMinGain + max:kMaxGain + unit:kAudioUnitParameterUnit_LinearGain + unitName:nil + flags:kAudioUnitParameterFlag_IsWritable | + kAudioUnitParameterFlag_IsReadable | + kAudioUnitParameterFlag_IsHighResolution + valueStrings:nil + dependentParameters:nil]; + + AUParameter *outputGainParam = [AUParameterTree createParameterWithIdentifier:@"outputGain" + name:@"Output Gain" + address:kJackTripAUParam_OutputGain + min:kMinGain + max:kMaxGain + unit:kAudioUnitParameterUnit_LinearGain + unitName:nil + flags:kAudioUnitParameterFlag_IsWritable | + kAudioUnitParameterFlag_IsReadable | + kAudioUnitParameterFlag_IsHighResolution + valueStrings:nil + dependentParameters:nil]; + + AUParameter *connectedParam = [AUParameterTree createParameterWithIdentifier:@"connected" + name:@"Connected" + address:kJackTripAUParam_Connected + min:0.0f + max:1.0f + unit:kAudioUnitParameterUnit_Boolean + unitName:nil + flags:kAudioUnitParameterFlag_IsReadable + valueStrings:nil + dependentParameters:nil]; + + // Set default values + sendGainParam.value = kDefaultSendGain; + outputMixParam.value = kDefaultOutputMix; + outputGainParam.value = kDefaultOutputGain; + connectedParam.value = 0.0f; + + // Create parameter tree + self.parameterTree = [AUParameterTree createTreeWithChildren:@[sendGainParam, outputMixParam, outputGainParam, connectedParam]]; + + // Set up parameter observers + __weak JackTripAU *weakSelf = self; + self.parameterTree.implementorValueObserver = ^(AUParameter *param, AUValue value) { + __strong JackTripAU *strongSelf = weakSelf; + if (strongSelf && strongSelf->mImpl) { + strongSelf->mImpl->setParameter(param.address, value); + } + }; + + self.parameterTree.implementorValueProvider = ^AUValue(AUParameter *param) { + __strong JackTripAU *strongSelf = weakSelf; + if (strongSelf && strongSelf->mImpl) { + return strongSelf->mImpl->getParameter(param.address); + } + return 0.0f; + }; +} + +- (BOOL)allocateRenderResourcesAndReturnError:(NSError **)outError { + if (![super allocateRenderResourcesAndReturnError:outError]) { + return NO; + } + + // Initialize C++ implementation + if (mImpl) { + mImpl->initialize(self.outputBus.format.sampleRate, self.maximumFramesToRender); + } + + // Create render block + __weak JackTripAU *weakSelf = self; + self.internalRenderBlock = ^AUAudioUnitStatus(AudioUnitRenderActionFlags *actionFlags, + const AudioTimeStamp *timestamp, + AVAudioFrameCount frameCount, + NSInteger outputBusNumber, + AudioBufferList *outputData, + const AURenderEvent *realtimeEventListHead, + AURenderPullInputBlock pullInputBlock) { + #pragma unused(actionFlags, outputBusNumber, realtimeEventListHead) + + __strong JackTripAU *strongSelf = weakSelf; + if (!strongSelf || !strongSelf->mImpl) { + return kAudioUnitErr_Uninitialized; + } + + // Pull input - create a temporary input buffer + AudioBufferList *inputData = nullptr; + UInt32 inputBufferSize = outputData->mBuffers[0].mDataByteSize; + AudioBufferList inputBufferList; + Float32 *inputBuffer0 = nullptr; + Float32 *inputBuffer1 = nullptr; + + if (pullInputBlock) { + // Allocate temporary input buffers + inputBuffer0 = (Float32 *)malloc(inputBufferSize); + inputBuffer1 = (Float32 *)malloc(inputBufferSize); + + inputBufferList.mNumberBuffers = 2; + inputBufferList.mBuffers[0].mNumberChannels = 1; + inputBufferList.mBuffers[0].mDataByteSize = inputBufferSize; + inputBufferList.mBuffers[0].mData = inputBuffer0; + inputBufferList.mBuffers[1].mNumberChannels = 1; + inputBufferList.mBuffers[1].mDataByteSize = inputBufferSize; + inputBufferList.mBuffers[1].mData = inputBuffer1; + + AudioUnitRenderActionFlags inputFlags = 0; + OSStatus status = pullInputBlock(&inputFlags, timestamp, frameCount, 0, &inputBufferList); + if (status != noErr) { + free(inputBuffer0); + free(inputBuffer1); + return status; + } + inputData = &inputBufferList; + } + + // Process audio through C++ implementation + strongSelf->mImpl->processAudio(inputData, outputData, frameCount); + + // Clean up temporary input buffers + if (inputBuffer0) free(inputBuffer0); + if (inputBuffer1) free(inputBuffer1); + + return noErr; + }; + + return YES; +} + +- (void)deallocateRenderResources { + if (mImpl) { + mImpl->uninitialize(); + } + + [super deallocateRenderResources]; +} + +- (NSArray *)MIDIOutputNames { + return @[]; +} + +- (NSArray *)channelCapabilities { + return @[]; +} + +#pragma mark - AUAudioUnitFactory + +- (NSIndexSet *)supportedViewConfigurations:(NSArray *)availableViewConfigurations { + // Return the first available view configuration + if (availableViewConfigurations.count > 0) { + return [NSIndexSet indexSetWithIndex:0]; + } + return [super supportedViewConfigurations:availableViewConfigurations]; +} + +- (void)requestViewControllerWithCompletionHandler:(void (^)(AUViewControllerBase * _Nullable))completionHandler { + JackTripAUViewController *viewController = [[JackTripAUViewController alloc] init]; + viewController.audioUnit = self; + completionHandler(viewController); +} + +@end \ No newline at end of file diff --git a/src/auv3/JackTripAUFactory.mm b/src/auv3/JackTripAUFactory.mm new file mode 100644 index 0000000..f549527 --- /dev/null +++ b/src/auv3/JackTripAUFactory.mm @@ -0,0 +1,40 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2024-2025 JackTrip Labs, Inc. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +#import +#import +#import "JackTripAU.h" + +// Audio Unit entry point +extern "C" AUAudioUnit *JackTripAUEntry(AudioComponentDescription desc) { + return [[JackTripAU alloc] initWithComponentDescription:desc + options:0 + error:nil]; +} \ No newline at end of file diff --git a/src/auv3/JackTripAUViewController.h b/src/auv3/JackTripAUViewController.h new file mode 100644 index 0000000..dca08a9 --- /dev/null +++ b/src/auv3/JackTripAUViewController.h @@ -0,0 +1,39 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2024-2025 JackTrip Labs, Inc. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +#pragma once + +#import + +@interface JackTripAUViewController : AUViewController + +@property (nonatomic, strong) AUAudioUnit *audioUnit; + +@end \ No newline at end of file diff --git a/src/auv3/JackTripAUViewController.mm b/src/auv3/JackTripAUViewController.mm new file mode 100644 index 0000000..6082766 --- /dev/null +++ b/src/auv3/JackTripAUViewController.mm @@ -0,0 +1,242 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2024-2025 JackTrip Labs, Inc. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +#import "JackTripAUViewController.h" +#import "JackTripAU.h" +#import + +@interface JackTripAUViewController () { + // UI Controls + NSSlider *sendGainSlider; + NSSlider *outputMixSlider; + NSSlider *outputGainSlider; + NSTextField *connectedLabel; + + // Parameter observers + AUParameterObserverToken parameterObserver; +} + +@end + +@implementation JackTripAUViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + // Set up the view + self.view = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 400, 300)]; + self.view.wantsLayer = YES; + self.view.layer.backgroundColor = [[NSColor controlBackgroundColor] CGColor]; + + [self createUI]; + [self connectParametersToControls]; +} + +- (void)createUI { + // Title label + NSTextField *titleLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(20, 250, 360, 30)]; + titleLabel.stringValue = @"JackTrip Audio Bridge"; + titleLabel.font = [NSFont boldSystemFontOfSize:18]; + titleLabel.textColor = [NSColor labelColor]; + titleLabel.backgroundColor = [NSColor clearColor]; + titleLabel.bordered = NO; + titleLabel.editable = NO; + titleLabel.alignment = NSTextAlignmentCenter; + [self.view addSubview:titleLabel]; + + // Send Gain control + NSTextField *sendGainLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(20, 200, 100, 20)]; + sendGainLabel.stringValue = @"Send Gain:"; + sendGainLabel.textColor = [NSColor labelColor]; + sendGainLabel.backgroundColor = [NSColor clearColor]; + sendGainLabel.bordered = NO; + sendGainLabel.editable = NO; + [self.view addSubview:sendGainLabel]; + + sendGainSlider = [[NSSlider alloc] initWithFrame:NSMakeRect(130, 200, 200, 20)]; + sendGainSlider.minValue = 0.0; + sendGainSlider.maxValue = 1.0; + sendGainSlider.doubleValue = 1.0; + sendGainSlider.continuous = YES; + sendGainSlider.target = self; + sendGainSlider.action = @selector(sendGainChanged:); + [self.view addSubview:sendGainSlider]; + + // Output Mix control + NSTextField *outputMixLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(20, 160, 100, 20)]; + outputMixLabel.stringValue = @"Output Mix:"; + outputMixLabel.textColor = [NSColor labelColor]; + outputMixLabel.backgroundColor = [NSColor clearColor]; + outputMixLabel.bordered = NO; + outputMixLabel.editable = NO; + [self.view addSubview:outputMixLabel]; + + outputMixSlider = [[NSSlider alloc] initWithFrame:NSMakeRect(130, 160, 200, 20)]; + outputMixSlider.minValue = 0.0; + outputMixSlider.maxValue = 1.0; + outputMixSlider.doubleValue = 0.0; + outputMixSlider.continuous = YES; + outputMixSlider.target = self; + outputMixSlider.action = @selector(outputMixChanged:); + [self.view addSubview:outputMixSlider]; + + // Output Gain control + NSTextField *outputGainLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(20, 120, 100, 20)]; + outputGainLabel.stringValue = @"Output Gain:"; + outputGainLabel.textColor = [NSColor labelColor]; + outputGainLabel.backgroundColor = [NSColor clearColor]; + outputGainLabel.bordered = NO; + outputGainLabel.editable = NO; + [self.view addSubview:outputGainLabel]; + + outputGainSlider = [[NSSlider alloc] initWithFrame:NSMakeRect(130, 120, 200, 20)]; + outputGainSlider.minValue = 0.0; + outputGainSlider.maxValue = 1.0; + outputGainSlider.doubleValue = 1.0; + outputGainSlider.continuous = YES; + outputGainSlider.target = self; + outputGainSlider.action = @selector(outputGainChanged:); + [self.view addSubview:outputGainSlider]; + + // Connection status + NSTextField *connectionLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(20, 80, 100, 20)]; + connectionLabel.stringValue = @"Status:"; + connectionLabel.textColor = [NSColor labelColor]; + connectionLabel.backgroundColor = [NSColor clearColor]; + connectionLabel.bordered = NO; + connectionLabel.editable = NO; + [self.view addSubview:connectionLabel]; + + connectedLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(130, 80, 200, 20)]; + connectedLabel.stringValue = @"Disconnected"; + connectedLabel.textColor = [NSColor systemRedColor]; + connectedLabel.backgroundColor = [NSColor clearColor]; + connectedLabel.bordered = NO; + connectedLabel.editable = NO; + [self.view addSubview:connectedLabel]; +} + +- (void)connectParametersToControls { + if (!self.audioUnit || !self.audioUnit.parameterTree) { + return; + } + + AUParameterTree *parameterTree = self.audioUnit.parameterTree; + + // Set up parameter observer + __weak JackTripAUViewController *weakSelf = self; + parameterObserver = [parameterTree tokenByAddingParameterObserver:^(AUParameterAddress address, AUValue value) { + JackTripAUViewController *strongSelf = weakSelf; + if (!strongSelf) return; + + dispatch_async(dispatch_get_main_queue(), ^{ + [strongSelf updateControlForParameter:address value:value]; + }); + }]; + + // Initialize control values from parameters + AUParameter *sendGainParam = [parameterTree parameterWithAddress:kJackTripAUParam_SendGain]; + if (sendGainParam) { + sendGainSlider.doubleValue = sendGainParam.value; + } + + AUParameter *outputMixParam = [parameterTree parameterWithAddress:kJackTripAUParam_OutputMix]; + if (outputMixParam) { + outputMixSlider.doubleValue = outputMixParam.value; + } + + AUParameter *outputGainParam = [parameterTree parameterWithAddress:kJackTripAUParam_OutputGain]; + if (outputGainParam) { + outputGainSlider.doubleValue = outputGainParam.value; + } + + AUParameter *connectedParam = [parameterTree parameterWithAddress:kJackTripAUParam_Connected]; + if (connectedParam) { + [self updateConnectionStatus:connectedParam.value > 0.5]; + } +} + +- (void)updateControlForParameter:(AUParameterAddress)address value:(AUValue)value { + switch (address) { + case kJackTripAUParam_SendGain: + sendGainSlider.doubleValue = value; + break; + case kJackTripAUParam_OutputMix: + outputMixSlider.doubleValue = value; + break; + case kJackTripAUParam_OutputGain: + outputGainSlider.doubleValue = value; + break; + case kJackTripAUParam_Connected: + [self updateConnectionStatus:value > 0.5]; + break; + } +} + +- (void)updateConnectionStatus:(BOOL)connected { + if (connected) { + connectedLabel.stringValue = @"Connected"; + connectedLabel.textColor = [NSColor systemGreenColor]; + } else { + connectedLabel.stringValue = @"Disconnected"; + connectedLabel.textColor = [NSColor systemRedColor]; + } +} + +// Control actions +- (void)sendGainChanged:(NSSlider *)sender { + AUParameter *param = [self.audioUnit.parameterTree parameterWithAddress:kJackTripAUParam_SendGain]; + if (param) { + param.value = sender.doubleValue; + } +} + +- (void)outputMixChanged:(NSSlider *)sender { + AUParameter *param = [self.audioUnit.parameterTree parameterWithAddress:kJackTripAUParam_OutputMix]; + if (param) { + param.value = sender.doubleValue; + } +} + +- (void)outputGainChanged:(NSSlider *)sender { + AUParameter *param = [self.audioUnit.parameterTree parameterWithAddress:kJackTripAUParam_OutputGain]; + if (param) { + param.value = sender.doubleValue; + } +} + +- (void)dealloc { + if (parameterObserver) { + [self.audioUnit.parameterTree removeParameterObserver:parameterObserver]; + parameterObserver = nil; + } +} + +@end \ No newline at end of file diff --git a/src/auv3/README.md b/src/auv3/README.md new file mode 100644 index 0000000..ed6e9b6 --- /dev/null +++ b/src/auv3/README.md @@ -0,0 +1,96 @@ +# JackTrip Audio Unit Plugin (v3) + +This directory contains the Audio Unit (AUv3) implementation of the JackTrip Audio Bridge plugin for macOS. + +## Overview + +The JackTrip Audio Bridge plugin uses the AudioBridgeProcessor class to exchange audio with the JackTrip application via local socket connections. This implementation is based on the working AUv2 plugin but adapted for the modern AUv3 architecture. It was created by asking AI to create a v3 version of the plugin. Although it compiles, it is not being recognized as a valid app extension. Apple doesn't provide good documentation or tools for debugging this. + +## Features + +- **Audio Processing**: Stereo audio input/output with configurable gain controls +- **Network Audio**: Exchange audio with remote JackTrip instances via local socket +- **Parameter Control**: Send gain, output mix, output gain, and connection status +- **Real-time Audio**: Low-latency audio processing suitable for live performance +- **Native UI**: Cocoa-based user interface with sliders for all parameters +- **C++ Core**: Maximum use of C++ for audio processing logic with minimal Objective-C wrapper + +## Architecture + +The AUv3 plugin follows a hybrid C++/Objective-C architecture: + +- `JackTripAU` - Main AUAudioUnit subclass (Objective-C wrapper) +- `JackTripAUImpl` - C++ implementation class containing all audio processing logic +- `AudioBridgeProcessor` - Shared C++ audio processing engine (same as AUv2/VST3) +- `JackTripAUViewController` - Native Cocoa UI controller +- `JackTripAUFactory` - Audio Unit component registration + +## Building + +The AUv3 plugin is built automatically when building JackTrip with static Qt on macOS: + +```bash +meson setup -Ddefault_library=static -Dnogui=true --buildtype release buildstatic +meson compile -C buildstatic +``` + +Use the `macos/assemble_app.sh` script to build the installation bundles. + +After installation, restart your DAW to detect the new plugin. + +## Usage + +1. Launch JackTrip in your desired mode +2. Insert the "JackTrip Audio Bridge" AUv3 plugin in your DAW +3. Configure the plugin parameters: + - **Send Gain**: Controls level of audio sent to remote (0.0 - 1.0) + - **Output Mix**: Blends received audio (0%) with input passthrough (100%) + - **Output Gain**: Master output level (0.0 - 1.0) + - **Connected**: Shows connection status (read-only indicator) + +The plugin will automatically attempt to connect to the JackTrip application via local socket. + +## Implementation Details + +### C++ Core Logic +- All audio processing is handled in C++ via `JackTripAUImpl` class +- Uses the same `AudioBridgeProcessor` as other plugin formats for consistency +- Parameter management and audio buffer handling in C++ +- Memory management with RAII and smart pointers + +### Objective-C Wrapper +- Minimal Objective-C code for AUv3 framework integration +- Property management and Audio Unit lifecycle +- Block-based render callback with proper memory management +- Parameter tree creation and observer setup + +### Audio Processing +- Stereo input/output processing +- Dynamic input buffer allocation in render callback +- Proper cleanup of temporary buffers +- Connection state monitoring and UI updates + +## Requirements + +- macOS 12 or later +- Audio Unit v3 compatible host application +- JackTrip application running on the same machine + +## Differences from AUv2 + +- Uses modern AUAudioUnit base class instead of AudioUnitSDK +- Block-based render callback instead of C-style callback +- AUParameterTree for parameter management +- Native AUv3 extension architecture +- Integrated UI support via AUViewController + +## References + +* [Audio Unit v3 Plugins](https://developer.apple.com/documentation/audiotoolbox/audio-unit-v3-plug-ins) +* [AUAudioUnit Class Reference](https://developer.apple.com/documentation/audiotoolbox/auaudiounit) +* [Core Audio Programming Guide](https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/CoreAudioOverview/Introduction/Introduction.html) + +## License + +Copyright (c) 2024-2025 JackTrip Labs, Inc. +Licensed under the MIT License. \ No newline at end of file diff --git a/src/auv3/meson.build b/src/auv3/meson.build new file mode 100644 index 0000000..75fdc0d --- /dev/null +++ b/src/auv3/meson.build @@ -0,0 +1,59 @@ +# Audio Unit v3 sources +auv3_sources = [ + 'JackTripAU.mm', + 'JackTripAUFactory.mm', + 'JackTripAUViewController.mm' +] + +# Required AudioBridgeProcessor sources +audio_bridge_sources = [ + '../AudioBridgeProcessor.cpp', + '../AudioSocket.cpp', + '../SocketClient.cpp', + '../ProcessPlugin.cpp', + '../jacktrip_globals.cpp' +] + +# Qt MOC for AudioSocket headers +audio_socket_moc_h = ['../AudioSocket.h', '../SocketClient.h', '../ProcessPlugin.h'] +audio_socket_sources = qt.compile_moc(headers: audio_socket_moc_h, extra_args: defines) + +# Apple frameworks required for Audio Units +auv3_frameworks = dependency('appleframeworks', modules: [ + 'Foundation', + 'AudioToolbox', + 'AudioUnit', + 'CoreAudioKit', + 'AVFoundation', + 'CoreAudio', + 'Cocoa', +]) + +# Dependencies +auv3_deps = [qt_core_deps, auv3_frameworks] +if get_option('default_library') == 'static' + auv3_deps += static_deps +endif + +# Add libsamplerate if available +if found_libsamplerate + auv3_deps += libsamplerate_dep +endif + +# Include directories +auv3_incdirs = include_directories('.', '../') + +# Build the Audio Unit bundle +jacktrip_auv3 = shared_module('JackTrip', + auv3_sources, audio_bridge_sources, audio_socket_sources, + name_prefix: '', + name_suffix: 'auv3', # Audio Unit extension + cpp_args: defines + ['-std=c++17'], + objcpp_args: defines + ['-fobjc-arc', '-std=c++17'], + dependencies: auv3_deps, + include_directories: auv3_incdirs, + link_args: static_link_args, + cpp_args: defines +) + +message('JackTrip Audio Unit v3 will be built') diff --git a/src/dblsqd/update_dialog.cpp b/src/dblsqd/update_dialog.cpp index bb914ef..1e6d867 100644 --- a/src/dblsqd/update_dialog.cpp +++ b/src/dblsqd/update_dialog.cpp @@ -637,13 +637,15 @@ void UpdateDialog::handleDownloadFinished() updateFilePath = file->fileName(); file->setAutoRemove(false); file->close(); + file->flush(); // Ensure all data is written to disk file->deleteLater(); setSettingsValue("updateFilePath", updateFilePath, settings); setSettingsValue("updateFileVersion", latestRelease.getVersion(), settings); if (accepted) { if (acceptedInstallButton == NULL) { - startUpdate(); + // Use QTimer::singleShot to ensure file is fully closed before opening + QTimer::singleShot(100, this, SLOT(startUpdate())); } else { emit installButtonClicked(acceptedInstallButton, updateFilePath); } diff --git a/src/dblsqd/update_dialog.h b/src/dblsqd/update_dialog.h index ce49ea8..ab6d0f7 100644 --- a/src/dblsqd/update_dialog.h +++ b/src/dblsqd/update_dialog.h @@ -6,6 +6,7 @@ #include #include #include +#include #include "feed.h" #include "ui_update_dialog.h" @@ -69,7 +70,6 @@ class UpdateDialog : public QDialog void adjustDialogSize(); void startDownload(); - virtual void startUpdate(); bool accepted; bool isDownloadFinished; @@ -99,6 +99,7 @@ class UpdateDialog : public QDialog void updateProgressBar(qint64, qint64); void autoDownloadCheckboxToggled(bool enabled = true); void onLinkActivated(QString link); + virtual void startUpdate(); }; } // namespace dblsqd diff --git a/src/gui/about.cpp b/src/gui/about.cpp index c235779..faf15f0 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -46,7 +46,7 @@ const QString About::s_buildID = QLatin1String(""); About::About(QWidget* parent) : QDialog(parent), m_ui(new Ui::About) { m_ui->setupUi(this); - connect(m_ui->closeButton, &QPushButton::clicked, this, [=]() { + connect(m_ui->closeButton, &QPushButton::clicked, this, [this]() { this->done(0); }); diff --git a/src/gui/qjacktrip.cpp b/src/gui/qjacktrip.cpp index 78790fd..11f3e5f 100644 --- a/src/gui/qjacktrip.cpp +++ b/src/gui/qjacktrip.cpp @@ -101,7 +101,7 @@ QJackTrip::QJackTrip(UserInterface& interface, QWidget* parent) connect(m_ui->certEdit, &QLineEdit::textChanged, this, &QJackTrip::authFilesChanged); connect(m_ui->keyEdit, &QLineEdit::textChanged, this, &QJackTrip::authFilesChanged); connect(m_ui->credsEdit, &QLineEdit::textChanged, this, &QJackTrip::authFilesChanged); - connect(m_ui->aboutButton, &QPushButton::clicked, this, [=]() { + connect(m_ui->aboutButton, &QPushButton::clicked, this, [this]() { About about(this); about.exec(); }); @@ -117,7 +117,7 @@ QJackTrip::QJackTrip(UserInterface& interface, QWidget* parent) m_ui->vsModeButton->setVisible(true); #endif connect(m_ui->autoPatchComboBox, QOverload::of(&QComboBox::currentIndexChanged), - this, [=]() { + this, [this]() { if (m_ui->autoPatchComboBox->currentIndex() == CLIENTFOFI || m_ui->autoPatchComboBox->currentIndex() == FULLMIX) { m_ui->patchServerCheckBox->setEnabled(true); @@ -125,14 +125,14 @@ QJackTrip::QJackTrip(UserInterface& interface, QWidget* parent) m_ui->patchServerCheckBox->setEnabled(false); } }); - connect(m_ui->authCheckBox, &QCHECKBOX_STATE_CHANGED, this, [=]() { + connect(m_ui->authCheckBox, &QCHECKBOX_STATE_CHANGED, this, [this]() { m_ui->usernameLabel->setEnabled(m_ui->authCheckBox->isChecked()); m_ui->usernameEdit->setEnabled(m_ui->authCheckBox->isChecked()); m_ui->passwordLabel->setEnabled(m_ui->authCheckBox->isChecked()); m_ui->passwordEdit->setEnabled(m_ui->authCheckBox->isChecked()); credentialsChanged(); }); - connect(m_ui->requireAuthCheckBox, &QCHECKBOX_STATE_CHANGED, this, [=]() { + connect(m_ui->requireAuthCheckBox, &QCHECKBOX_STATE_CHANGED, this, [this]() { m_ui->certLabel->setEnabled(m_ui->requireAuthCheckBox->isChecked()); m_ui->certEdit->setEnabled(m_ui->requireAuthCheckBox->isChecked()); m_ui->certBrowse->setEnabled(m_ui->requireAuthCheckBox->isChecked()); @@ -144,21 +144,21 @@ QJackTrip::QJackTrip(UserInterface& interface, QWidget* parent) m_ui->credsBrowse->setEnabled(m_ui->requireAuthCheckBox->isChecked()); authFilesChanged(); }); - connect(m_ui->ioStatsCheckBox, &QCHECKBOX_STATE_CHANGED, this, [=]() { + connect(m_ui->ioStatsCheckBox, &QCHECKBOX_STATE_CHANGED, this, [this]() { m_ui->ioStatsLabel->setEnabled(m_ui->ioStatsCheckBox->isChecked()); m_ui->ioStatsSpinBox->setEnabled(m_ui->ioStatsCheckBox->isChecked()); if (!m_ui->ioStatsCheckBox->isChecked()) { m_statsDialog->hide(); } }); - connect(m_ui->verboseCheckBox, &QCHECKBOX_STATE_CHANGED, this, [=]() { + connect(m_ui->verboseCheckBox, &QCHECKBOX_STATE_CHANGED, this, [this]() { gVerboseFlag = m_ui->verboseCheckBox->isChecked(); if (!gVerboseFlag) { m_debugDialog->hide(); m_debugDialog->clearOutput(); } }); - connect(m_ui->jitterCheckBox, &QCHECKBOX_STATE_CHANGED, this, [=]() { + connect(m_ui->jitterCheckBox, &QCHECKBOX_STATE_CHANGED, this, [this]() { m_ui->broadcastCheckBox->setEnabled(m_ui->jitterCheckBox->isChecked()); m_ui->broadcastQueueLabel->setEnabled(m_ui->jitterCheckBox->isChecked() && m_ui->broadcastCheckBox->isChecked()); @@ -183,13 +183,13 @@ QJackTrip::QJackTrip(UserInterface& interface, QWidget* parent) m_autoQueueIndicator.setText(QStringLiteral("Auto queue: disabled")); } }); - connect(m_ui->broadcastCheckBox, &QCHECKBOX_STATE_CHANGED, this, [=]() { + connect(m_ui->broadcastCheckBox, &QCHECKBOX_STATE_CHANGED, this, [this]() { m_ui->broadcastQueueLabel->setEnabled(m_ui->jitterCheckBox->isChecked() && m_ui->broadcastCheckBox->isChecked()); m_ui->broadcastQueueSpinBox->setEnabled(m_ui->jitterCheckBox->isChecked() && m_ui->broadcastCheckBox->isChecked()); }); - connect(m_ui->autoQueueCheckBox, &QCHECKBOX_STATE_CHANGED, this, [=]() { + connect(m_ui->autoQueueCheckBox, &QCHECKBOX_STATE_CHANGED, this, [this]() { m_ui->autoQueueLabel->setEnabled(m_ui->jitterCheckBox->isChecked() && m_ui->autoQueueCheckBox->isChecked()); m_ui->autoQueueSpinBox->setEnabled(m_ui->jitterCheckBox->isChecked() @@ -205,34 +205,34 @@ QJackTrip::QJackTrip(UserInterface& interface, QWidget* parent) } }); - connect(m_ui->inFreeverbCheckBox, &QCHECKBOX_STATE_CHANGED, this, [=]() { + connect(m_ui->inFreeverbCheckBox, &QCHECKBOX_STATE_CHANGED, this, [this]() { m_ui->inFreeverbLabel->setEnabled(m_ui->inFreeverbCheckBox->isChecked()); m_ui->inFreeverbWetnessSlider->setEnabled(m_ui->inFreeverbCheckBox->isChecked()); }); - connect(m_ui->inZitarevCheckBox, &QCHECKBOX_STATE_CHANGED, this, [=]() { + connect(m_ui->inZitarevCheckBox, &QCHECKBOX_STATE_CHANGED, this, [this]() { m_ui->inZitarevLabel->setEnabled(m_ui->inZitarevCheckBox->isChecked()); m_ui->inZitarevWetnessSlider->setEnabled(m_ui->inZitarevCheckBox->isChecked()); }); - connect(m_ui->outFreeverbCheckBox, &QCHECKBOX_STATE_CHANGED, this, [=]() { + connect(m_ui->outFreeverbCheckBox, &QCHECKBOX_STATE_CHANGED, this, [this]() { m_ui->outFreeverbLabel->setEnabled(m_ui->outFreeverbCheckBox->isChecked()); m_ui->outFreeverbWetnessSlider->setEnabled( m_ui->outFreeverbCheckBox->isChecked()); }); - connect(m_ui->outZitarevCheckBox, &QCHECKBOX_STATE_CHANGED, this, [=]() { + connect(m_ui->outZitarevCheckBox, &QCHECKBOX_STATE_CHANGED, this, [this]() { m_ui->outZitarevLabel->setEnabled(m_ui->outZitarevCheckBox->isChecked()); m_ui->outZitarevWetnessSlider->setEnabled(m_ui->outZitarevCheckBox->isChecked()); }); - connect(m_ui->outLimiterCheckBox, &QCHECKBOX_STATE_CHANGED, this, [=]() { + connect(m_ui->outLimiterCheckBox, &QCHECKBOX_STATE_CHANGED, this, [this]() { m_ui->outLimiterLabel->setEnabled(m_ui->outLimiterCheckBox->isChecked()); m_ui->outClientsSpinBox->setEnabled(m_ui->outLimiterCheckBox->isChecked()); }); - connect(m_ui->connectScriptCheckBox, &QCHECKBOX_STATE_CHANGED, this, [=]() { + connect(m_ui->connectScriptCheckBox, &QCHECKBOX_STATE_CHANGED, this, [this]() { m_ui->connectScriptEdit->setEnabled(m_ui->connectScriptCheckBox->isChecked()); m_ui->connectScriptBrowse->setEnabled(m_ui->connectScriptCheckBox->isChecked()); }); - connect(m_ui->disconnectScriptCheckBox, &QCHECKBOX_STATE_CHANGED, this, [=]() { + connect(m_ui->disconnectScriptCheckBox, &QCHECKBOX_STATE_CHANGED, this, [this]() { m_ui->disconnectScriptEdit->setEnabled( m_ui->disconnectScriptCheckBox->isChecked()); m_ui->disconnectScriptBrowse->setEnabled( @@ -260,7 +260,7 @@ QJackTrip::QJackTrip(UserInterface& interface, QWidget* parent) #ifdef RT_AUDIO connect(m_ui->backendComboBox, QOverload::of(&QComboBox::currentIndexChanged), - this, [=](int index) { + this, [this](int index) { if (index == 1) { m_ui->sampleRateComboBox->setEnabled(true); m_ui->sampleRateLabel->setEnabled(true); @@ -287,7 +287,7 @@ QJackTrip::QJackTrip(UserInterface& interface, QWidget* parent) m_ui->backendWarningLabel->setVisible(false); } }); - connect(m_ui->refreshDevicesButton, &QPushButton::clicked, this, [=]() { + connect(m_ui->refreshDevicesButton, &QPushButton::clicked, this, [this]() { populateDeviceMenu(m_ui->inputDeviceComboBox, true); populateDeviceMenu(m_ui->outputDeviceComboBox, false); }); @@ -432,9 +432,10 @@ void QJackTrip::showEvent(QShowEvent* event) "will automatically be re-enabled.)"); msgBox.setWindowTitle(QStringLiteral("JACK Not Available")); msgBox.setCheckBox(dontBugMe); - QObject::connect(dontBugMe, &QCHECKBOX_STATE_CHANGED, this, [=]() { - m_hideWarning = dontBugMe->isChecked(); - }); + QObject::connect(dontBugMe, &QCHECKBOX_STATE_CHANGED, this, + [this, dontBugMe]() { + m_hideWarning = dontBugMe->isChecked(); + }); msgBox.exec(); if (m_hideWarning) { settings.setValue(QStringLiteral("HideJackWarning"), true); diff --git a/src/images/Dual_LED.png b/src/images/Dual_LED.png new file mode 100644 index 0000000..45458f2 Binary files /dev/null and b/src/images/Dual_LED.png differ diff --git a/src/images/Sercan_Moog_Knob.png b/src/images/Sercan_Moog_Knob.png new file mode 100644 index 0000000..fdab8b5 Binary files /dev/null and b/src/images/Sercan_Moog_Knob.png differ diff --git a/src/images/background.png b/src/images/background.png new file mode 100644 index 0000000..2c7effa Binary files /dev/null and b/src/images/background.png differ diff --git a/src/images/background_2x.png b/src/images/background_2x.png new file mode 100644 index 0000000..7e1312d Binary files /dev/null and b/src/images/background_2x.png differ diff --git a/src/images/icon_64.png b/src/images/icon_64.png new file mode 100644 index 0000000..9e965dc Binary files /dev/null and b/src/images/icon_64.png differ diff --git a/src/images/jacktrip.icns b/src/images/jacktrip.icns new file mode 100644 index 0000000..532faa2 Binary files /dev/null and b/src/images/jacktrip.icns differ diff --git a/src/jacktrip_globals.h b/src/jacktrip_globals.h index aa9f9e9..d11dddc 100644 --- a/src/jacktrip_globals.h +++ b/src/jacktrip_globals.h @@ -40,7 +40,7 @@ #include "jacktrip_types.h" -constexpr const char* const gVersion = "2.6.0"; ///< JackTrip version +constexpr const char* const gVersion = "2.7.1"; ///< JackTrip version //******************************************************************************* /// \name Default Values diff --git a/src/vs/ChangeDevices.qml b/src/vs/ChangeDevices.qml index 349e1f8..cd428d0 100644 --- a/src/vs/ChangeDevices.qml +++ b/src/vs/ChangeDevices.qml @@ -41,8 +41,6 @@ Rectangle { property string linkText: virtualstudio.darkMode ? "#8B8D8D" : "#272525" - property bool autoQueueBuffer: virtualstudio.queueBuffer == 0 - function getQueueBufferString () { let queueBuffer = virtualstudio.queueBuffer; if (useStudioQueueBuffer.checkState == Qt.Checked) { @@ -203,21 +201,16 @@ Rectangle { anchors.left: useStudioQueueBuffer.left background: Rectangle { radius: 6 * virtualstudio.uiScale - color: queueBufferAutoButton.down ? browserButtonPressedColour : (queueBufferAutoButton.hovered ? browserButtonHoverColour : (autoQueueBuffer ? "#FF0000" : browserButtonColour)) + color: virtualstudio.queueBuffer == 0 ? (virtualstudio.darkMode ? "#FFFFFF" : "#000000") : browserButtonColour } onClicked: { - if (autoQueueBuffer) { - virtualstudio.queueBuffer = 5; - } else { - virtualstudio.queueBuffer = 0; - } - autoQueueBuffer = !autoQueueBuffer; + virtualstudio.queueBuffer = virtualstudio.queueBuffer == 0 ? 5 : 0; } Text { text: "Auto" font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale} anchors { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter } - color: textColour + color: virtualstudio.queueBuffer == 0 ? (virtualstudio.darkMode ? "#000000" : "#FFFFFF") : textColour } visible: useStudioQueueBuffer.checkState != Qt.Checked @@ -233,7 +226,8 @@ Rectangle { to: 250 stepSize: 1 padding: 0 - visible: !autoQueueBuffer && useStudioQueueBuffer.checkState != Qt.Checked + visible: useStudioQueueBuffer.checkState != Qt.Checked + enabled: virtualstudio.queueBuffer != 0 anchors.top: useStudioQueueBuffer.bottom anchors.topMargin: 16 * virtualstudio.uiScale @@ -267,6 +261,7 @@ Rectangle { radius: 13 * virtualstudio.uiScale color: queueBufferSlider.pressed ? sliderPressedColour : sliderColour border.color: buttonStroke + visible: virtualstudio.queueBuffer != 0 } } @@ -279,7 +274,7 @@ Rectangle { text: "Lower Latency" font { family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale } color: textColour - visible: !autoQueueBuffer && useStudioQueueBuffer.checkState != Qt.Checked + visible: useStudioQueueBuffer.checkState != Qt.Checked } Text { @@ -291,7 +286,7 @@ Rectangle { text: "Higher Quality" font { family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale } color: textColour - visible: !autoQueueBuffer && useStudioQueueBuffer.checkState != Qt.Checked + visible: useStudioQueueBuffer.checkState != Qt.Checked } } diff --git a/src/vs/CreateStudio.qml b/src/vs/CreateStudio.qml index c796dae..7c68874 100644 --- a/src/vs/CreateStudio.qml +++ b/src/vs/CreateStudio.qml @@ -36,10 +36,9 @@ Item { WebEngineView { id: webEngineView anchors.fill: parent + settings.fullScreenSupportEnabled: true settings.javascriptCanAccessClipboard: true settings.javascriptCanPaste: true - settings.screenCaptureEnabled: true - profile.httpUserAgent: `JackTrip/${virtualstudio.versionString}` url: `https://${virtualstudio.apiHost === "test.jacktrip.com" ? "next-test.jacktrip.com" : "www.jacktrip.com"}/app/studios/create` onContextMenuRequested: function(request) { diff --git a/src/vs/JTApplication.h b/src/vs/JTApplication.h index 8b62c2e..edc0998 100644 --- a/src/vs/JTApplication.h +++ b/src/vs/JTApplication.h @@ -3,7 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2022-2024 JackTrip Labs, Inc. + Copyright (c) 2022-2025 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/vs/OldWebEngine.qml b/src/vs/OldWebEngine.qml new file mode 100644 index 0000000..e3077ed --- /dev/null +++ b/src/vs/OldWebEngine.qml @@ -0,0 +1,126 @@ +import QtQuick +import QtQuick.Controls +import QtWebEngine +import QtQuick.Dialogs + +Item { + width: parent.width; height: parent.height + clip: true + + function contentScriptFactory (port) { + return ` + // add script tag for qwebchannel + document.head.addEventListener("initqwebchannel", () => { + + var script = document.createElement("script"); + script.onload = function () { + var url = "ws://localhost:${port}"; + var socket = new WebSocket(url); + + socket.onclose = function() { + console.error("[QT] web channel closed"); + }; + socket.onerror = function(event) { + console.error("[QT] web channel error: " + event.type); + }; + socket.onopen = function() { + new QWebChannel(socket, function(channel) { + console.log("[QT] Socket opened"); + + // make core object accessible globally + window.virtualstudio = channel.objects.virtualstudio; + window.auth = channel.objects.auth; + window.clipboard = channel.objects.clipboard; + + const event = new CustomEvent("qwebchannelinitialized"); + document.head.dispatchEvent(event); + console.log("[QT] Dispatched qwebchannelinitialized event"); + console.log("[QT] Connected to WebChannel, ready to send/receive messages!"); + }); + } + } + script.setAttribute("src", "qrc:///qtwebchannel/qwebchannel.js"); + script.setAttribute("type", "text/javascript"); + document.head.appendChild(script); + console.log("[QT] Added qwebchannel initialization script to DOM."); + }); + console.log("[QT] Added initqwebchannel event listener"); + ` + } + + Rectangle { + id: web + anchors.fill: parent + color: backgroundColour + + property string studioId: virtualstudio.currentStudio.id + + WebEngineView { + id: webEngineView + anchors.fill: parent + settings.fullScreenSupportEnabled: true + settings.javascriptCanAccessClipboard: true + settings.javascriptCanPaste: true + settings.screenCaptureEnabled: true + settings.playbackRequiresUserGesture: false + url: `https://${virtualstudio.apiHost}/studios/${web.studioId}/live` + + // useful for debugging + // onJavaScriptConsoleMessage: function(level, message, lineNumber, sourceID) { + // console.log(level, message, lineNumber, sourceID); + // } + + // useful for debugging + // onLoadingChanged: function(loadRequest) { + // console.log("onLoadingChanged", loadRequest.errorCode, loadRequest.errorDomain, loadRequest.errorString, loadRequest.status, loadRequest.url); + // } + + onContextMenuRequested: function(request) { + // this disables the default context menu: https://doc.qt.io/qt-6.2/qml-qtwebengine-contextmenurequest.html#accepted-prop + request.accepted = true; + } + + onNewWindowRequested: function(request) { + Qt.openUrlExternally(request.requestedUrl); + } + + onFeaturePermissionRequested: function(securityOrigin, feature) { + webEngineView.grantFeaturePermission(securityOrigin, feature, true); + } + + onRenderProcessTerminated: function(terminationStatus, exitCode) { + var status = ""; + switch (terminationStatus) { + case WebEngineView.NormalTerminationStatus: + status = "(normal exit)"; + break; + case WebEngineView.AbnormalTerminationStatus: + status = "(abnormal exit)"; + break; + case WebEngineView.CrashedTerminationStatus: + status = "(crashed)"; + break; + case WebEngineView.KilledTerminationStatus: + status = "(killed)"; + break; + } + console.log("Render process exited with code " + exitCode + " " + status); + } + + onNavigationRequested: function(request) { + webEngineView.userScripts.collection = [ + { + name: "script", + sourceCode: contentScriptFactory(virtualstudio.webChannelPort), + injectionPoint: WebEngineScript.DocumentReady, + worldId: WebEngineScript.MainWorld + } + ] + } + } + } + + ScreenShareModal { + id: screenShareModal + } +} \ No newline at end of file diff --git a/src/vs/ScreenShareModal.qml b/src/vs/ScreenShareModal.qml new file mode 100644 index 0000000..6f97650 --- /dev/null +++ b/src/vs/ScreenShareModal.qml @@ -0,0 +1,256 @@ +import QtQuick +import QtQuick.Controls + +Item { + anchors.centerIn: parent + width: 480 * virtualstudio.uiScale + + property var mediaRequest: null + + // Theme-aware colors matching DeviceWarningModal + property string textColour: virtualstudio.darkMode ? "#FAFBFB" : "#0F0D0D" + property string buttonColour: virtualstudio.darkMode ? "#494646" : "#EAECEC" + property string buttonPressedStroke: virtualstudio.darkMode ? "#827D7D" : "#BABCBC" + property string buttonPressedColour: virtualstudio.darkMode ? "#524F4F" : "#DEE0E0" + property string buttonStroke: virtualstudio.darkMode ? "#80827D7D" : "#34979797" + property string buttonHoverStroke: virtualstudio.darkMode ? "#7B7777" : "#BABCBC" + property string buttonHoverColour: virtualstudio.darkMode ? "#5B5858" : "#D3D4D4" + + Popup { + id: screenSharePopup + padding: 1 + width: parent.width + height: 400 * virtualstudio.uiScale + anchors.centerIn: parent + modal: true + focus: true + + background: Rectangle { + anchors.fill: parent + color: "transparent" + radius: 6 * virtualstudio.uiScale + border.width: 1 + border.color: buttonStroke + clip: true + } + + contentItem: Rectangle { + id: screenShareContent + width: parent.width + height: parent.height + color: backgroundColour + radius: 6 * virtualstudio.uiScale + + Text { + id: titleText + text: "What would you like to share?" + font.family: "Poppins" + font.pixelSize: 12 * virtualstudio.fontScale * virtualstudio.uiScale + font.bold: true + color: textColour + anchors.top: parent.top + anchors.topMargin: 16 * virtualstudio.uiScale + anchors.left: parent.left + anchors.leftMargin: 24 * virtualstudio.uiScale + } + + Button { + id: cancelButton + width: 160 * virtualstudio.uiScale + height: 30 * virtualstudio.uiScale + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: 16 * virtualstudio.uiScale + + onClicked: { + if (mediaRequest) { + mediaRequest.cancel(); + } + screenSharePopup.close(); + } + + background: Rectangle { + radius: 6 * virtualstudio.uiScale + color: parent.down ? buttonPressedColour : (parent.hovered ? buttonHoverColour : buttonColour) + border.width: 1 + border.color: parent.down ? buttonPressedStroke : (parent.hovered ? buttonHoverStroke : buttonStroke) + } + + contentItem: Text { + text: "Cancel" + font.family: "Poppins" + font.pixelSize: 10 * virtualstudio.fontScale * virtualstudio.uiScale + color: textColour + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + } + + ScrollView { + id: scrollingSection + x: 24 * virtualstudio.uiScale + y: titleText.y + titleText.height + 16 * virtualstudio.uiScale + width: screenShareContent.width - 48 * virtualstudio.uiScale + height: screenShareContent.height - titleText.height - cancelButton.height - 64 * virtualstudio.uiScale + clip: true + + Column { + width: scrollingSection.width - 20 * virtualstudio.uiScale + spacing: 8 * virtualstudio.uiScale + + // Screens section + Column { + width: parent.width + spacing: 4 * virtualstudio.uiScale + visible: mediaRequest && mediaRequest.screensModel.rowCount() > 0 + + Text { + text: "Screens:" + font.family: "Poppins" + font.pixelSize: 10 * virtualstudio.fontScale * virtualstudio.uiScale + font.bold: true + color: textColour + } + + Repeater { + model: mediaRequest ? mediaRequest.screensModel : null + delegate: Rectangle { + width: parent.width + height: 36 * virtualstudio.uiScale + color: screensMouseArea.containsMouse ? buttonHoverColour : buttonColour + border.color: screensMouseArea.containsMouse ? buttonHoverStroke : buttonStroke + border.width: 1 + radius: 6 * virtualstudio.uiScale + + Row { + anchors.left: parent.left + anchors.leftMargin: 12 * virtualstudio.uiScale + anchors.verticalCenter: parent.verticalCenter + spacing: 12 * virtualstudio.uiScale + + Rectangle { + width: 20 * virtualstudio.uiScale + height: 20 * virtualstudio.uiScale + color: "#4CAF50" + radius: 3 * virtualstudio.uiScale + anchors.verticalCenter: parent.verticalCenter + + Text { + anchors.centerIn: parent + text: "🖥️" + font.pixelSize: 10 * virtualstudio.fontScale * virtualstudio.uiScale + color: "white" + } + } + + Text { + text: display || ("Screen " + (index + 1)) + anchors.verticalCenter: parent.verticalCenter + font.family: "Poppins" + font.pixelSize: 10 * virtualstudio.fontScale * virtualstudio.uiScale + color: textColour + elide: Text.ElideRight + width: parent.parent.width - (60 * virtualstudio.uiScale) + } + } + + MouseArea { + id: screensMouseArea + anchors.fill: parent + hoverEnabled: true + onClicked: { + if (mediaRequest) { + var screenIndex = mediaRequest.screensModel.index(index, 0); + mediaRequest.selectScreen(screenIndex); + screenSharePopup.close(); + } + } + } + } + } + } + + // Windows section + Column { + width: parent.width + spacing: 4 * virtualstudio.uiScale + visible: mediaRequest && mediaRequest.windowsModel.rowCount() > 0 + + Text { + text: "Windows:" + font.family: "Poppins" + font.pixelSize: 10 * virtualstudio.fontScale * virtualstudio.uiScale + font.bold: true + color: textColour + } + + Repeater { + model: mediaRequest ? mediaRequest.windowsModel : null + delegate: Rectangle { + width: parent.width + height: 36 * virtualstudio.uiScale + color: windowsMouseArea.containsMouse ? buttonHoverColour : buttonColour + border.color: windowsMouseArea.containsMouse ? buttonHoverStroke : buttonStroke + border.width: 1 + radius: 6 * virtualstudio.uiScale + + Row { + anchors.left: parent.left + anchors.leftMargin: 12 * virtualstudio.uiScale + anchors.verticalCenter: parent.verticalCenter + spacing: 12 * virtualstudio.uiScale + + Rectangle { + width: 20 * virtualstudio.uiScale + height: 20 * virtualstudio.uiScale + color: "#2196F3" + radius: 3 * virtualstudio.uiScale + anchors.verticalCenter: parent.verticalCenter + + Text { + anchors.centerIn: parent + text: "🪟" + font.pixelSize: 10 * virtualstudio.fontScale * virtualstudio.uiScale + color: "white" + } + } + + Text { + text: display || ("Window " + (index + 1)) + anchors.verticalCenter: parent.verticalCenter + font.family: "Poppins" + font.pixelSize: 10 * virtualstudio.fontScale * virtualstudio.uiScale + color: textColour + elide: Text.ElideRight + width: parent.parent.width - (60 * virtualstudio.uiScale) + } + } + + MouseArea { + id: windowsMouseArea + anchors.fill: parent + hoverEnabled: true + onClicked: { + if (mediaRequest) { + var windowIndex = mediaRequest.windowsModel.index(index, 0); + mediaRequest.selectWindow(windowIndex); + screenSharePopup.close(); + } + } + } + } + } + } + } + } + } + } + + function open() { + screenSharePopup.open(); + } + + function close() { + screenSharePopup.close(); + } +} \ No newline at end of file diff --git a/src/vs/Web.qml b/src/vs/Web.qml index 13927f8..1706d74 100644 --- a/src/vs/Web.qml +++ b/src/vs/Web.qml @@ -3,7 +3,33 @@ import QtQuick.Controls Loader { anchors.fill: parent - source: "WebEngine.qml" + + // Function to compare version strings properly + function compareVersions(version1, version2) { + var v1parts = version1.split('.'); + var v2parts = version2.split('.'); + var maxLength = Math.max(v1parts.length, v2parts.length); + + for (var i = 0; i < maxLength; i++) { + var v1part = parseInt(v1parts[i] || '0'); + var v2part = parseInt(v2parts[i] || '0'); + + if (v1part < v2part) return -1; + if (v1part > v2part) return 1; + } + return 0; + } + + // Extract Qt version from buildString (format: "Qt version 6.x.x") + function getQtVersion() { + var buildStr = virtualstudio.buildString; + var match = buildStr.match(/Qt version (\d+\.\d+\.\d+)/); + return match ? match[1] : "6.0.0"; // fallback to 6.0.0 if not found + } + + // Use OldWebEngine.qml for versions before 6.7.0 (which don't have onDesktopMediaRequested) + // Use WebEngine.qml for 6.7.0 and later (which support onDesktopMediaRequested) + source: compareVersions(getQtVersion(), "6.7.0") >= 0 ? "WebEngine.qml" : "OldWebEngine.qml" // TODO: Add support for QtWebView // source: useWebEngine ? "WebEngine.qml" : "WebView.qml" diff --git a/src/vs/WebEngine.qml b/src/vs/WebEngine.qml index 192d184..9fb4f2e 100644 --- a/src/vs/WebEngine.qml +++ b/src/vs/WebEngine.qml @@ -1,6 +1,7 @@ import QtQuick import QtQuick.Controls import QtWebEngine +import QtQuick.Dialogs Item { width: parent.width; height: parent.height @@ -57,10 +58,11 @@ Item { WebEngineView { id: webEngineView anchors.fill: parent + settings.fullScreenSupportEnabled: true settings.javascriptCanAccessClipboard: true settings.javascriptCanPaste: true settings.screenCaptureEnabled: true - profile.httpUserAgent: `JackTrip/${virtualstudio.versionString}` + settings.playbackRequiresUserGesture: false url: `https://${virtualstudio.apiHost}/studios/${web.studioId}/live` // useful for debugging @@ -86,6 +88,14 @@ Item { webEngineView.grantFeaturePermission(securityOrigin, feature, true); } + onDesktopMediaRequested: function(request) { + // Store the request for later use + screenShareModal.mediaRequest = request; + + // Show the modal + screenShareModal.open(); + } + onRenderProcessTerminated: function(terminationStatus, exitCode) { var status = ""; switch (terminationStatus) { @@ -117,4 +127,8 @@ Item { } } } -} + + ScreenShareModal { + id: screenShareModal + } +} \ No newline at end of file diff --git a/src/vs/WebView.qml b/src/vs/WebView.qml index 8590542..de20d95 100644 --- a/src/vs/WebView.qml +++ b/src/vs/WebView.qml @@ -15,7 +15,6 @@ Item { WebView { id: webEngineView anchors.fill: parent - httpUserAgent: `JackTrip/${virtualstudio.versionString}` url: `https://${virtualstudio.apiHost}/studios/${web.studioId}/live` } } diff --git a/src/vs/virtualstudio.cpp b/src/vs/virtualstudio.cpp index 7ba6368..d685645 100644 --- a/src/vs/virtualstudio.cpp +++ b/src/vs/virtualstudio.cpp @@ -3,7 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2022-2024 JackTrip Labs, Inc. + Copyright (c) 2022-2025 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -54,7 +55,10 @@ #include #include #include +#include +#include #include +#include #include #include @@ -135,19 +139,19 @@ VirtualStudio::VirtualStudio(UserInterface& parent) &VirtualStudio::slotAuthSucceeded); connect(m_auth.data(), &VsAuth::updatedAccessToken, this, &VirtualStudio::slotAccessTokenUpdated); - connect(m_auth.data(), &VsAuth::refreshTokenFailed, this, [=]() { + connect(m_auth.data(), &VsAuth::refreshTokenFailed, this, [this]() { m_auth->authenticate(QStringLiteral("")); // retry without using refresh token }); - connect(m_auth.data(), &VsAuth::fetchUserInfoFailed, this, [=]() { + connect(m_auth.data(), &VsAuth::fetchUserInfoFailed, this, [this]() { m_auth->authenticate(QStringLiteral("")); // retry without using refresh token }); - connect(m_auth.data(), &VsAuth::deviceCodeExpired, this, [=]() { + connect(m_auth.data(), &VsAuth::deviceCodeExpired, this, [this]() { m_auth->authenticate(QStringLiteral("")); // retry without using refresh token }); m_webChannelServer.reset(new QWebSocketServer( QStringLiteral("Qt6 Virtual Studio Server"), QWebSocketServer::NonSecureMode)); - connect(m_webChannelServer.data(), &QWebSocketServer::newConnection, this, [=]() { + connect(m_webChannelServer.data(), &QWebSocketServer::newConnection, this, [this]() { m_webChannel->connectTo( new WebSocketTransport(m_webChannelServer->nextPendingConnection())); }); @@ -181,7 +185,7 @@ VirtualStudio::VirtualStudio(UserInterface& parent) // on window focus, attempt to refresh the access token if the token is more than 1 // hour old - connect(m_view.data(), &VsQuickView::focusGained, this, [=]() { + connect(m_view.data(), &VsQuickView::focusGained, this, [this]() { QString refreshToken = m_auth->refreshToken(); if (refreshToken.isEmpty()) { return; @@ -290,16 +294,27 @@ VirtualStudio::VirtualStudio(UserInterface& parent) // prepare handler for local socket connections m_socketServerPtr.reset(new SocketServer()); - m_socketServerPtr->addHandler("deeplink", [=](QSharedPointer& socket) { - m_deepLinkPtr->handleVsDeeplinkRequest(socket); - }); - m_socketServerPtr->addHandler("audio", [=](QSharedPointer& socket) { + m_socketServerPtr->addHandler("deeplink", + [this](QSharedPointer& socket) { + m_deepLinkPtr->handleVsDeeplinkRequest(socket); + }); + m_socketServerPtr->addHandler("audio", [this](QSharedPointer& socket) { this->handleAudioSocketRequest(socket); }); m_socketServerPtr->start(); - // initialize default QtWebEngineProfile - m_qwebEngineProfile = QWebEngineProfile::defaultProfile(); + // initialize default profile for WebEngine + QQuickWebEngineProfile* defaultWebEngineProfile = + QQuickWebEngineProfile::defaultProfile(); + defaultWebEngineProfile->setStorageName(QStringLiteral("Default")); + defaultWebEngineProfile->setHttpUserAgent( + QStringLiteral("JackTrip/%1").arg(versionString())); + defaultWebEngineProfile->setCachePath(defaultWebEngineProfile->persistentStoragePath() + + QStringLiteral("/Cache")); + defaultWebEngineProfile->setPersistentCookiesPolicy( + QQuickWebEngineProfile::ForcePersistentCookies); + defaultWebEngineProfile->setHttpCacheType(QQuickWebEngineProfile::DiskHttpCache); + defaultWebEngineProfile->setOffTheRecord(false); } void VirtualStudio::show() @@ -400,10 +415,10 @@ QString VirtualStudio::copyrightString() #endif result += - "Copyright © 2008-2024 Juan-Pablo Caceres, Chris Chafe, et al. SoundWIRE " + "Copyright © 2008-2025 Juan-Pablo Caceres, Chris Chafe, et al. SoundWIRE " "group at CCRMA, Stanford University.

\n"; result += - "Virtual Studio interface and integration Copyright © 2022-2024 JackTrip " + "Virtual Studio interface and integration Copyright © 2022-2025 JackTrip " "Labs, Inc.

\n"; if (hasClassicMode()) { @@ -1316,6 +1331,13 @@ void VirtualStudio::handleAudioSocketRequest(QSharedPointer& socke { QSharedPointer audioSocketPtr(new AudioSocket(socket)); m_audioConfigPtr->registerAudioSocket(audioSocketPtr); + if (!m_jackTripRunning || m_devicePtr.isNull()) { + if (m_audioConfigPtr->getAudioReady()) { + // no need to refresh or validate devices, just restart audio + m_audioConfigPtr->restartAudio(); + } + return; + } triggerReconnect(true); } @@ -1387,24 +1409,14 @@ void VirtualStudio::slotAuthSucceeded() void VirtualStudio::slotAccessTokenUpdated(QString accessToken) { - // set cookie - QWebEngineCookieStore* cookieStore = m_qwebEngineProfile->cookieStore(); - QNetworkCookie authCookie = - QNetworkCookie(QByteArray("auth_code"), accessToken.toUtf8()); + // set auth cookies for prod mode + setCookie("auth_code", accessToken, "https://www.jacktrip.com"); + setCookie("auth_code", accessToken, "https://app.jacktrip.com"); - QUrl url1 = QUrl(QStringLiteral("https://www.jacktrip.com")); - QUrl url2 = QUrl(QStringLiteral("https://app.jacktrip.com")); - QUrl url3 = QUrl(QStringLiteral("http://localhost:3000")); - if (testMode()) { - url1 = QUrl(QStringLiteral("https://next-test.jacktrip.com")); - url2 = QUrl(QStringLiteral("https://test.jacktrip.com")); - } - - cookieStore->setCookie(authCookie, url1); - cookieStore->setCookie(authCookie, url2); - if (testMode()) { - cookieStore->setCookie(authCookie, url3); - } + // set auth cookies for test mode + setCookie("auth_code", accessToken, "https://next-test.jacktrip.com"); + setCookie("auth_code", accessToken, "https://test.jacktrip.com"); + setCookie("auth_code", accessToken, "http://localhost:3000"); // Get refresh token and userId m_refreshToken = m_auth->refreshToken(); @@ -1417,6 +1429,16 @@ void VirtualStudio::slotAccessTokenUpdated(QString accessToken) settings.endGroup(); } +void VirtualStudio::setCookie(const QString& name, const QString& value, + const QString& origin) +{ + // set webengine cookie + QNetworkCookie cookie = QNetworkCookie(name.toUtf8(), value.toUtf8()); + QWebEngineCookieStore* cookieStore = + QWebEngineProfile::defaultProfile()->cookieStore(); + cookieStore->setCookie(cookie, origin); +} + void VirtualStudio::connectionFinished() { if (!m_devicePtr.isNull() @@ -1441,8 +1463,10 @@ void VirtualStudio::connectionFinished() void VirtualStudio::processError(const QString& errorMessage) { static const QString RtAudioErrorMsg = QStringLiteral("RtAudio Error"); + static const QString RtApiErrorMsg = QStringLiteral("RtApiCore: "); static const QString JackAudioErrorMsg = QStringLiteral("The Jack server was shut down"); + static const QString DisconnectedErrorMsg = QStringLiteral("device was disconnected"); const bool shouldSwitchToRtAudio = (errorMessage == QLatin1String("Maybe the JACK server is not running?")); @@ -1459,7 +1483,7 @@ void VirtualStudio::processError(const QString& errorMessage) } else if (errorMessage.startsWith(RtAudioErrorMsg)) { if (errorMessage.length() > RtAudioErrorMsg.length() + 2) { const QString details(errorMessage.sliced(RtAudioErrorMsg.length() + 2)); - if (details.contains(QStringLiteral("device was disconnected")) + if (details.contains(DisconnectedErrorMsg) || details.contains( QStringLiteral("Unable to retrieve capture buffer"))) { msgBox.setText(QStringLiteral("Your audio interface was disconnected.")); @@ -1477,6 +1501,13 @@ void VirtualStudio::processError(const QString& errorMessage) msgBox.setText(QStringLiteral("The JACK Audio Server was stopped.")); } msgBox.setWindowTitle(QStringLiteral("Jack Audio Error")); + } else if (errorMessage.startsWith(RtApiErrorMsg)) { + if (errorMessage.contains(DisconnectedErrorMsg)) { + msgBox.setText(QStringLiteral("Your audio interface was disconnected.")); + } else { + msgBox.setText(errorMessage.sliced(RtApiErrorMsg.length())); + } + msgBox.setWindowTitle(QStringLiteral("Audio Interface Error")); } else { msgBox.setText(QStringLiteral("Error: ").append(errorMessage)); msgBox.setWindowTitle(QStringLiteral("Doh!")); @@ -1518,12 +1549,10 @@ void VirtualStudio::handleWebsocketMessage(const QString& msg) return; } - bool currentStudioUpdated = false; - bool serverHostOrPortUpdated = false; + bool currentStudioUpdated = false; if (serverHost != m_currentStudio.host()) { m_currentStudio.setHost(serverHost); - currentStudioUpdated = true; - serverHostOrPortUpdated = true; + currentStudioUpdated = true; } if (serverStatus != m_currentStudio.status()) { m_currentStudio.setStatus(serverStatus); @@ -1543,8 +1572,7 @@ void VirtualStudio::handleWebsocketMessage(const QString& msg) } if (serverPort != m_currentStudio.port()) { m_currentStudio.setPort(serverPort); - currentStudioUpdated = true; - serverHostOrPortUpdated = true; + currentStudioUpdated = true; } if (queueBuffer != m_currentStudio.queueBuffer()) { m_currentStudio.setQueueBuffer(queueBuffer); @@ -1889,18 +1917,24 @@ void VirtualStudio::detectedFeedbackLoop() VirtualStudio::~VirtualStudio() { QDesktopServices::unsetUrlHandler("jacktrip"); + // close the window m_view.reset(); + // stop the audio worker thread before destructing other things - if (!m_audioConfigPtr.isNull()) { - m_audioConfigPtr->disconnect(); - m_audioConfigPtr.reset(); - } + m_audioConfigPtr->stopWorker(); + // stop device and corresponding threads if (!m_devicePtr.isNull()) { m_devicePtr->disconnect(); m_devicePtr.reset(); } + + // reset VsAudio after VsDevice since it holds a smart pointer + if (!m_audioConfigPtr.isNull()) { + m_audioConfigPtr->disconnect(); + m_audioConfigPtr.reset(); + } } QApplication* VirtualStudio::createApplication(int& argc, char* argv[]) diff --git a/src/vs/virtualstudio.h b/src/vs/virtualstudio.h index 35987ff..fd4c243 100644 --- a/src/vs/virtualstudio.h +++ b/src/vs/virtualstudio.h @@ -3,7 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2022-2024 JackTrip Labs, Inc. + Copyright (c) 2022-2025 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -41,7 +41,6 @@ #include #include #include -#include #include #include #include @@ -51,8 +50,6 @@ #include #include #include -#include -#include #include #include "../Settings.h" @@ -259,6 +256,7 @@ class VirtualStudio : public QObject private slots: void slotAuthSucceeded(); void slotAccessTokenUpdated(QString accessToken); + void setCookie(const QString& name, const QString& value, const QString& origin); void receivedConnectionFromPeer(); void handleWebsocketMessage(const QString& msg); void restartStudioSocket(); @@ -289,7 +287,6 @@ class VirtualStudio : public QObject UserInterface& m_interface; VsServerInfo m_currentStudio; QNetworkAccessManager* m_networkAccessManagerPtr; - QWebEngineProfile* m_qwebEngineProfile; QSharedPointer m_socketServerPtr; QScopedPointer m_view; QSharedPointer m_deepLinkPtr; diff --git a/src/vs/vs.qrc b/src/vs/vs.qrc index 945ce72..9a12bea 100644 --- a/src/vs/vs.qrc +++ b/src/vs/vs.qrc @@ -26,10 +26,12 @@ DeviceRefreshButton.qml DeviceWarning.qml DeviceWarningModal.qml + ScreenShareModal.qml InfoTooltip.qml Web.qml WebView.qml WebEngine.qml + OldWebEngine.qml WebNull.qml FeedbackSurvey.qml AppIcon.qml diff --git a/src/vs/vsApi.cpp b/src/vs/vsApi.cpp index 74fdaff..7e3695b 100644 --- a/src/vs/vsApi.cpp +++ b/src/vs/vsApi.cpp @@ -3,7 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2022-2024 JackTrip Labs, Inc. + Copyright (c) 2022-2025 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/vs/vsApi.h b/src/vs/vsApi.h index d66cab8..c9aca29 100644 --- a/src/vs/vsApi.h +++ b/src/vs/vsApi.h @@ -3,7 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2022-2024 JackTrip Labs, Inc. + Copyright (c) 2022-2025 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/vs/vsAudio.cpp b/src/vs/vsAudio.cpp index 09e34e9..4665cb2 100644 --- a/src/vs/vsAudio.cpp +++ b/src/vs/vsAudio.cpp @@ -3,7 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2022-2024 JackTrip Labs, Inc. + Copyright (c) 2022-2025 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -176,12 +176,18 @@ VsAudio::VsAudio(QObject* parent) } VsAudio::~VsAudio() +{ + stopWorker(); +} + +void VsAudio::stopWorker() { if (m_workerThreadPtr == nullptr) return; m_workerThreadPtr->quit(); WaitForSignal(m_workerThreadPtr, &QThread::finished); m_workerThreadPtr->deleteLater(); + m_workerThreadPtr = nullptr; } bool VsAudio::backendAvailable() const @@ -1122,9 +1128,9 @@ void VsAudioWorker::getDeviceList(const QVector& devices, channels.clear(); list.clear(); - // do not include blacklisted audio interfaces + // do not include blocklisted audio interfaces // these are known to be unstable and cause JackTrip to crash - QVector blacklisted_devices = { + QVector blocklisted_devices = { #ifdef _WIN32 // Realtek ASIO: seems to crash any computer that tries to use it QString::fromUtf8("Realtek ASIO"), @@ -1153,11 +1159,14 @@ void VsAudioWorker::getDeviceList(const QVector& devices, continue; } - // Skip blacklisted devices + // Skip blocklisted devices + // Apple Inc.: iPhone (10) Microphone + // Apple Inc.: Mike's iPhone Microphone const bool iPhoneMic = deviceName.startsWith("Apple Inc.:") - && deviceName.endsWith("Phone Microphone"); - if (blacklisted_devices.contains(deviceName) || iPhoneMic) { - std::cout << "RTAudio: blacklisted " << (isInput ? "input" : "output") + && deviceName.contains("iPhone") + && deviceName.endsWith("Microphone"); + if (blocklisted_devices.contains(deviceName) || iPhoneMic) { + std::cout << "RTAudio: blocklisted " << (isInput ? "input" : "output") << " device: " << devices[n].name << std::endl; continue; } @@ -1316,7 +1325,7 @@ void VsAudioWorker::validateInputDevicesState() element.insert(QString::fromStdString("numChannels"), QVariant(1).toInt()); inputChannelsComboModel.push_back(element); } - for (int i = 0; i < numDevicesChannelsAvailable; i++) { + for (int i = 0; i < numDevicesChannelsAvailable - 1; i++) { QJsonObject element = QJsonObject(); element.insert( QString::fromStdString("label"), @@ -1423,7 +1432,7 @@ void VsAudioWorker::validateOutputDevicesState() // set the output channels selector to have the options based on the currently // selected device QJsonArray outputChannelsComboModel; - for (int i = 0; i < numDevicesChannelsAvailable; i++) { + for (int i = 0; i < numDevicesChannelsAvailable - 1; i++) { QJsonObject element = QJsonObject(); element.insert( QString::fromStdString("label"), diff --git a/src/vs/vsAudio.h b/src/vs/vsAudio.h index 3521d51..5acaf76 100644 --- a/src/vs/vsAudio.h +++ b/src/vs/vsAudio.h @@ -3,7 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2022-2024 JackTrip Labs, Inc. + Copyright (c) 2022-2025 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -145,6 +145,7 @@ class VsAudio : public QObject // allow VirtualStudio to get Permissions to bind to QML view VsPermissions& getPermissions() { return *m_permissionsPtr; } VsAudioWorker& getWorker() { return *m_audioWorkerPtr; } + void stopWorker(); // allow VirtualStudio to create new audio interfaces AudioInterface* newAudioInterface(JackTrip* jackTripPtr = nullptr); diff --git a/src/vs/vsAuth.cpp b/src/vs/vsAuth.cpp index 0b558a5..653121d 100644 --- a/src/vs/vsAuth.cpp +++ b/src/vs/vsAuth.cpp @@ -3,7 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2022-2024 JackTrip Labs, Inc. + Copyright (c) 2022-2025 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -93,7 +93,7 @@ void VsAuth::initializedCodeFlow(QString code, QString verificationUrl) void VsAuth::fetchUserInfo(QString accessToken) { QNetworkReply* reply = m_api->getAuth0UserInfo(); - connect(reply, &QNetworkReply::finished, this, [=]() { + connect(reply, &QNetworkReply::finished, this, [this, reply, accessToken]() { if (reply->error() != QNetworkReply::NoError) { std::cout << "VsAuth::fetchUserInfo Error: " << reply->errorString().toStdString() << std::endl; @@ -138,7 +138,7 @@ void VsAuth::refreshAccessToken(QString refreshToken) // send request QNetworkReply* reply = m_networkAccessManager->post(request, data.toUtf8()); - connect(reply, &QNetworkReply::finished, this, [=]() { + connect(reply, &QNetworkReply::finished, this, [this, reply]() { QByteArray buffer = reply->readAll(); // Error: failed to get device code diff --git a/src/vs/vsAuth.h b/src/vs/vsAuth.h index e075478..10f7b53 100644 --- a/src/vs/vsAuth.h +++ b/src/vs/vsAuth.h @@ -3,7 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2022-2024 JackTrip Labs, Inc. + Copyright (c) 2022-2025 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/vs/vsConstants.h b/src/vs/vsConstants.h index 83bb423..a4b8aed 100644 --- a/src/vs/vsConstants.h +++ b/src/vs/vsConstants.h @@ -3,7 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2022-2024 JackTrip Labs, Inc. + Copyright (c) 2022-2025 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/vs/vsDeeplink.cpp b/src/vs/vsDeeplink.cpp index f1448a4..69ba1ce 100644 --- a/src/vs/vsDeeplink.cpp +++ b/src/vs/vsDeeplink.cpp @@ -3,7 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2022-2024 JackTrip Labs, Inc. + Copyright (c) 2022-2025 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/vs/vsDeeplink.h b/src/vs/vsDeeplink.h index faa0192..70b2d8c 100644 --- a/src/vs/vsDeeplink.h +++ b/src/vs/vsDeeplink.h @@ -3,7 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2022-2024 JackTrip Labs, Inc. + Copyright (c) 2022-2025 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/vs/vsDevice.cpp b/src/vs/vsDevice.cpp index 39d7cc9..93aef49 100644 --- a/src/vs/vsDevice.cpp +++ b/src/vs/vsDevice.cpp @@ -3,7 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2022-2024 JackTrip Labs, Inc. + Copyright (c) 2022-2025 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -81,7 +81,7 @@ void VsDevice::registerApp() // check if device exists QNetworkReply* reply = m_api->getDevice(m_appID); - connect(reply, &QNetworkReply::finished, this, [=]() { + connect(reply, &QNetworkReply::finished, this, [this, reply]() { // Got error if (reply->error() != QNetworkReply::NoError) { QVariant statusCode = @@ -255,7 +255,7 @@ void VsDevice::updateState(const QString& serverId) }; QJsonDocument request = QJsonDocument(json); QNetworkReply* reply = m_api->updateDevice(m_appID, request.toJson()); - connect(reply, &QNetworkReply::finished, this, [=]() { + connect(reply, &QNetworkReply::finished, this, [reply]() { if (reply->error() != QNetworkReply::NoError) { std::cout << "Error: " << reply->errorString().toStdString() << std::endl; } @@ -281,7 +281,7 @@ void VsDevice::sendLevels() QJsonDocument request = QJsonDocument(json); QNetworkReply* reply = m_api->updateDevice(m_appID, request.toJson()); - connect(reply, &QNetworkReply::finished, this, [=]() { + connect(reply, &QNetworkReply::finished, this, [reply]() { if (reply->error() != QNetworkReply::NoError) { std::cout << "Error: " << reply->errorString().toStdString() << std::endl; } @@ -563,7 +563,7 @@ void VsDevice::registerJTAsDevice() QJsonDocument request = QJsonDocument(json); QNetworkReply* reply = m_api->postDevice(request.toJson()); - connect(reply, &QNetworkReply::finished, this, [=]() { + connect(reply, &QNetworkReply::finished, this, [this, reply]() { if (reply->error() != QNetworkReply::NoError) { std::cout << "Error: " << reply->errorString().toStdString() << std::endl; reply->deleteLater(); diff --git a/src/vs/vsDevice.h b/src/vs/vsDevice.h index 64878ba..a39b636 100644 --- a/src/vs/vsDevice.h +++ b/src/vs/vsDevice.h @@ -3,7 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2022-2024 JackTrip Labs, Inc. + Copyright (c) 2022-2025 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/vs/vsDeviceCodeFlow.cpp b/src/vs/vsDeviceCodeFlow.cpp index 4becfea..e05e333 100644 --- a/src/vs/vsDeviceCodeFlow.cpp +++ b/src/vs/vsDeviceCodeFlow.cpp @@ -3,7 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2022-2024 JackTrip Labs, Inc. + Copyright (c) 2022-2025 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -81,7 +81,7 @@ void VsDeviceCodeFlow::initDeviceAuthorizationCodeFlow() // send request QNetworkReply* reply = m_netManager->post(request, data.toUtf8()); - connect(reply, &QNetworkReply::finished, this, [=]() { + connect(reply, &QNetworkReply::finished, this, [this, reply]() { bool success = processDeviceCodeNetworkReply(reply); if (success) { // notify success along with user code and verification URL @@ -136,7 +136,7 @@ void VsDeviceCodeFlow::onPollingTimerTick() // send send request for token QNetworkReply* reply = m_netManager->post(request, data.toUtf8()); - connect(reply, &QNetworkReply::finished, this, [=]() { + connect(reply, &QNetworkReply::finished, this, [this, reply]() { bool success = processPollingOAuthTokenNetworkReply(reply); if (m_authenticationError) { // shouldn't happen diff --git a/src/vs/vsDeviceCodeFlow.h b/src/vs/vsDeviceCodeFlow.h index 0bd91d0..2a28f07 100644 --- a/src/vs/vsDeviceCodeFlow.h +++ b/src/vs/vsDeviceCodeFlow.h @@ -3,7 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2022-2024 JackTrip Labs, Inc. + Copyright (c) 2022-2025 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/vs/vsMacPermissions.h b/src/vs/vsMacPermissions.h index 05fd873..ad91586 100644 --- a/src/vs/vsMacPermissions.h +++ b/src/vs/vsMacPermissions.h @@ -3,7 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2022-2024 JackTrip Labs, Inc. + Copyright (c) 2022-2025 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/vs/vsMacPermissions.mm b/src/vs/vsMacPermissions.mm index 46e3f36..4aca837 100644 --- a/src/vs/vsMacPermissions.mm +++ b/src/vs/vsMacPermissions.mm @@ -3,7 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2022-2024 JackTrip Labs, Inc. + Copyright (c) 2022-2025 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/vs/vsPermissions.cpp b/src/vs/vsPermissions.cpp index b8c07db..a9d08e7 100644 --- a/src/vs/vsPermissions.cpp +++ b/src/vs/vsPermissions.cpp @@ -3,7 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2022-2024 JackTrip Labs, Inc. + Copyright (c) 2022-2025 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/vs/vsPermissions.h b/src/vs/vsPermissions.h index f95c5b5..f9ddd7f 100644 --- a/src/vs/vsPermissions.h +++ b/src/vs/vsPermissions.h @@ -3,7 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2022-2024 JackTrip Labs, Inc. + Copyright (c) 2022-2025 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/vs/vsPing.cpp b/src/vs/vsPing.cpp index 58b6b79..4d8d725 100644 --- a/src/vs/vsPing.cpp +++ b/src/vs/vsPing.cpp @@ -3,7 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2022-2024 JackTrip Labs, Inc. + Copyright (c) 2022-2025 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/vs/vsPing.h b/src/vs/vsPing.h index fa4d3f8..90d4807 100644 --- a/src/vs/vsPing.h +++ b/src/vs/vsPing.h @@ -3,7 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2022-2024 JackTrip Labs, Inc. + Copyright (c) 2022-2025 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/vs/vsPinger.cpp b/src/vs/vsPinger.cpp index beae7fb..8583ea2 100644 --- a/src/vs/vsPinger.cpp +++ b/src/vs/vsPinger.cpp @@ -3,7 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2022-2024 JackTrip Labs, Inc. + Copyright (c) 2022-2025 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/vs/vsPinger.h b/src/vs/vsPinger.h index a093a53..659a50c 100644 --- a/src/vs/vsPinger.h +++ b/src/vs/vsPinger.h @@ -3,7 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2022-2024 JackTrip Labs, Inc. + Copyright (c) 2022-2025 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/vs/vsQmlClipboard.h b/src/vs/vsQmlClipboard.h index 1bd544e..8e386d3 100644 --- a/src/vs/vsQmlClipboard.h +++ b/src/vs/vsQmlClipboard.h @@ -3,7 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2022-2024 JackTrip Labs, Inc. + Copyright (c) 2022-2025 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/vs/vsQuickView.cpp b/src/vs/vsQuickView.cpp index 1c7dbc4..1940f83 100644 --- a/src/vs/vsQuickView.cpp +++ b/src/vs/vsQuickView.cpp @@ -3,7 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2022-2024 JackTrip Labs, Inc. + Copyright (c) 2022-2025 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/vs/vsQuickView.h b/src/vs/vsQuickView.h index eef2d0f..6d56406 100644 --- a/src/vs/vsQuickView.h +++ b/src/vs/vsQuickView.h @@ -3,7 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2022-2024 JackTrip Labs, Inc. + Copyright (c) 2022-2025 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/vs/vsServerInfo.cpp b/src/vs/vsServerInfo.cpp index 094e323..f63fbc1 100644 --- a/src/vs/vsServerInfo.cpp +++ b/src/vs/vsServerInfo.cpp @@ -3,7 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2022-2024 JackTrip Labs, Inc. + Copyright (c) 2022-2025 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/vs/vsServerInfo.h b/src/vs/vsServerInfo.h index 48aac8b..2c8a6c7 100644 --- a/src/vs/vsServerInfo.h +++ b/src/vs/vsServerInfo.h @@ -3,7 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2022-2024 JackTrip Labs, Inc. + Copyright (c) 2022-2025 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/vs/vsWebSocket.cpp b/src/vs/vsWebSocket.cpp index 39cd625..b4876d5 100644 --- a/src/vs/vsWebSocket.cpp +++ b/src/vs/vsWebSocket.cpp @@ -3,7 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2022-2024 JackTrip Labs, Inc. + Copyright (c) 2022-2025 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/vs/vsWebSocket.h b/src/vs/vsWebSocket.h index b5e4c47..cc28ef6 100644 --- a/src/vs/vsWebSocket.h +++ b/src/vs/vsWebSocket.h @@ -3,7 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2022-2024 JackTrip Labs, Inc. + Copyright (c) 2022-2025 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/vst3/Info.plist b/src/vst3/Info.plist new file mode 100644 index 0000000..20f5792 --- /dev/null +++ b/src/vst3/Info.plist @@ -0,0 +1,34 @@ + + + + + LSMinimumSystemVersion + 12.0 + CFBundleDevelopmentRegion + en + CFBundleExecutable + JackTrip.vst3 + CFBundleDisplayName + JackTrip Audio Bridge + CFBundleGetInfoString + JackTrip Audio Bridge + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + BNDL + CFBundleSignature + ???? + CFBundleIdentifier + %BUNDLEID% + CFBundleName + %BUNDLENAME% + CFBundleVersion + %VERSION% + CFBundleShortVersionString + %VERSION% + CSResourcesFileMapped + + NSHumanReadableCopyright + Copyright © 2024-2025 JackTrip Labs, Inc. + + diff --git a/src/vst3/JackTripVST.h b/src/vst3/JackTripVST.h index b7ad310..b946c5d 100644 --- a/src/vst3/JackTripVST.h +++ b/src/vst3/JackTripVST.h @@ -39,7 +39,7 @@ #define JackTripVSTVST3Category "Fx" #define stringOriginalFilename "JackTrip.vst3" #define stringFileDescription "JackTrip VST3" -#define stringCompanyName "JackTrip Labs\0" +#define stringCompanyName "JackTrip\0" #define stringLegalCopyright "Copyright (c) 2024-2025 JackTrip Labs, Inc." #define stringLegalTrademarks "VST is a trademark of Steinberg Media Technologies GmbH" diff --git a/src/vst3/JackTripVSTEntry.cpp b/src/vst3/JackTripVSTEntry.cpp index bbff0c4..2db0f09 100644 --- a/src/vst3/JackTripVSTEntry.cpp +++ b/src/vst3/JackTripVSTEntry.cpp @@ -49,8 +49,7 @@ using namespace Steinberg; // GetPluginFactory function! //------------------------------------------------------------------------ -BEGIN_FACTORY_DEF("JackTrip Labs", "https://www.jacktrip.com", - "mailto:support@jacktrip.com") +BEGIN_FACTORY_DEF("JackTrip", "https://www.jacktrip.com", "mailto:support@jacktrip.com") //---First Plug-in included in this factory------- // its kVstAudioEffectClass component diff --git a/src/vst3/JackTripVSTProcessor.cpp b/src/vst3/JackTripVSTProcessor.cpp index 5659b28..af24adf 100644 --- a/src/vst3/JackTripVSTProcessor.cpp +++ b/src/vst3/JackTripVSTProcessor.cpp @@ -33,7 +33,6 @@ #include "JackTripVSTProcessor.h" -#include "../AudioSocket.h" #include "JackTripVST.h" #include "JackTripVSTDataBlock.h" #include "base/source/fstreamer.h" @@ -42,49 +41,6 @@ using namespace std; using namespace Steinberg; -// uncomment to generate log file, for debugging purposes -// #define JACKTRIP_VST_LOG - -#ifdef JACKTRIP_VST_LOG -#if defined(_WIN32) -#define JACKTRIP_VST_LOG_PATH "c:/JackTripTemp" -#define JACKTRIP_VST_LOG_FILE "c:/JackTripTemp/vst.log" -#else -#define JACKTRIP_VST_LOG_PATH "/tmp/jacktrip" -#define JACKTRIP_VST_LOG_FILE "/tmp/jacktrip/vst.log" -#endif -#include -#include -#include - -static ofstream kLogFile; - -void qtMessageHandler([[maybe_unused]] QtMsgType type, - [[maybe_unused]] const QMessageLogContext& context, - const QString& msg) -{ - kLogFile << msg.toStdString() << endl; -} -#endif - -// any multiplier less than this is considered to be silent -constexpr double kSilentMul = 0.0000001; - -static QCoreApplication* sQtAppPtr = nullptr; - -static QCoreApplication* getQtAppPtr() -{ - if (sQtAppPtr == nullptr) { - sQtAppPtr = QCoreApplication::instance(); - if (sQtAppPtr == nullptr) { - int argc = 0; - sQtAppPtr = new QCoreApplication(argc, nullptr); - sQtAppPtr->setAttribute(Qt::AA_NativeWindows); - } - } - return sQtAppPtr; -} - //------------------------------------------------------------------------ // JackTripVSTProcessor //------------------------------------------------------------------------ @@ -115,34 +71,7 @@ tresult PLUGIN_API JackTripVSTProcessor::initialize(FUnknown* context) addAudioInput(STR16("Stereo In"), Vst::SpeakerArr::kStereo); addAudioOutput(STR16("Stereo Out"), Vst::SpeakerArr::kStereo); - getQtAppPtr(); - - mInputBuffer = new float*[AudioSocketNumChannels]; - mOutputBuffer = new float*[AudioSocketNumChannels]; - for (int i = 0; i < AudioSocketNumChannels; i++) { - mInputBuffer[i] = new float[AudioSocketMaxSamplesPerBlock]; - mOutputBuffer[i] = new float[AudioSocketMaxSamplesPerBlock]; - } - -#ifdef JACKTRIP_VST_LOG - if (!filesystem::is_directory(JACKTRIP_VST_LOG_PATH)) { - if (!filesystem::create_directory(JACKTRIP_VST_LOG_PATH)) { - qDebug() << "Failed to create VST log directory: " << JACKTRIP_VST_LOG_PATH; - } - } - kLogFile.open(JACKTRIP_VST_LOG_FILE, ios::app); - if (kLogFile.is_open()) { - kLogFile << "JackTrip VST initialized" << endl; - kLogFile.flush(); - cout.rdbuf(kLogFile.rdbuf()); - cerr.rdbuf(kLogFile.rdbuf()); - } else { - qDebug() << "Failed to open VST log file: " << JACKTRIP_VST_LOG_FILE; - } - qInstallMessageHandler(qtMessageHandler); -#endif - - qDebug() << "JackTrip VST initialized"; + // qDebug() << "JackTrip VST initialized"; return kResultOk; } @@ -150,20 +79,9 @@ tresult PLUGIN_API JackTripVSTProcessor::initialize(FUnknown* context) //------------------------------------------------------------------------ tresult PLUGIN_API JackTripVSTProcessor::terminate() { - mSocketPtr.reset(); - - for (int i = 0; i < AudioSocketNumChannels; i++) { - delete[] mInputBuffer[i]; - delete[] mOutputBuffer[i]; - } - delete[] mInputBuffer; - delete[] mOutputBuffer; + mProcessor.uninitialize(); - qDebug() << "JackTrip VST terminated"; - -#ifdef JACKTRIP_VST_LOG - kLogFile.close(); -#endif + // qDebug() << "JackTrip VST terminated"; //---do not forget to call parent ------ return AudioEffect::terminate(); @@ -257,28 +175,22 @@ tresult PLUGIN_API JackTripVSTProcessor::setActive(TBool state) if (mSampleRate == 0 || mBufferSize == 0) { return kResultFalse; } - // create a audio new socket - if (mSocketPtr.isNull()) { - // not yet initialized - mSocketPtr.reset(new AudioSocket(true)); - // automatically retry to establish connection - mSocketPtr->setRetryConnection(true); - mSocketPtr->connect(mSampleRate, mBufferSize); - } + // initialize the audio bridge processor + mProcessor.initialize(mSampleRate, mBufferSize); // activate data exchange API if (!mDataExchangePtr.isNull()) { mDataExchangePtr->onActivate(processSetup); } } else { - // disconnect from remote when inactive - mSocketPtr.reset(); + // uninitialize the audio bridge processor + mProcessor.uninitialize(); // deactivate data exchange API if (!mDataExchangePtr.isNull()) { mDataExchangePtr->onDeactivate(); } } - qDebug() << "JackTrip VST setActive(" << int(state) << ")"; + // qDebug() << "JackTrip VST setActive(" << int(state) << ")"; //--- called when the Plug-in is enable/disable (On/Off) ----- return AudioEffect::setActive(state); @@ -287,17 +199,13 @@ tresult PLUGIN_API JackTripVSTProcessor::setActive(TBool state) //------------------------------------------------------------------------ tresult PLUGIN_API JackTripVSTProcessor::setProcessing(TBool state) { - qDebug() << "JackTrip VST setProcessing(" << int(state) << ")"; + // qDebug() << "JackTrip VST setProcessing(" << int(state) << ")"; return AudioEffect::setProcessing(state); } //------------------------------------------------------------------------ tresult PLUGIN_API JackTripVSTProcessor::process(Vst::ProcessData& data) { - // sanity check; should never happen - if (mSocketPtr.isNull()) - return kResultFalse; - //--- Read inputs parameter changes----------- if (data.inputParameterChanges) { int32 numParamsChanged = data.inputParameterChanges->getParameterCount(); @@ -341,17 +249,8 @@ tresult PLUGIN_API JackTripVSTProcessor::process(Vst::ProcessData& data) updateVolumeMultipliers(); } -#if 0 - if (mLogFile.is_open()) { - mLogFile << "JackTrip VST process: inputs=" << data.numInputs - << ", outputs=" << data.numOutputs - << ", samples=" << data.numSamples - << endl; - } -#endif - // handle connection state change - if (mConnected != mSocketPtr->isConnected()) { + if (mConnected != mProcessor.isEstablished()) { // try both methods because some hosts only support one or the other. // first try to use data output parameters, if available. bool updatedConnectedState = false; @@ -360,7 +259,7 @@ tresult PLUGIN_API JackTripVSTProcessor::process(Vst::ProcessData& data) Steinberg::Vst::IParamValueQueue* paramQueue = data.outputParameterChanges->addParameterData(kParamConnectedId, index); if (paramQueue) { - int8 connectedState = mSocketPtr->isConnected() ? 1 : 0; + int8 connectedState = mProcessor.isEstablished() ? 1 : 0; int32 index2 = 0; if (paramQueue->addPoint(0, connectedState, index2) == kResultOk) { updatedConnectedState = true; @@ -373,7 +272,7 @@ tresult PLUGIN_API JackTripVSTProcessor::process(Vst::ProcessData& data) acquireNewExchangeBlock(); } if (auto block = toDataBlock(mCurrentExchangeBlock)) { - block->connectedState = mSocketPtr->isConnected(); + block->connectedState = mProcessor.isEstablished(); if (mDataExchangePtr->sendCurrentBlock()) { updatedConnectedState = true; } @@ -383,18 +282,13 @@ tresult PLUGIN_API JackTripVSTProcessor::process(Vst::ProcessData& data) } if (updatedConnectedState) { // we can update our state after successfully deliver the change - mConnected = mSocketPtr->isConnected(); + mConnected = mProcessor.isEstablished(); } } //--- Process Audio--------------------- //--- ---------------------------------- - if (data.numInputs == 0 || data.numOutputs == 0) { - // nothing to do - return kResultOk; - } - - if (data.numSamples <= 0) { + if (data.numSamples <= 0 || data.numInputs == 0 || data.numOutputs == 0) { // nothing to do return kResultOk; } @@ -416,82 +310,70 @@ tresult PLUGIN_API JackTripVSTProcessor::process(Vst::ProcessData& data) return kResultOk; } - // clear buffers - for (int i = 0; i < AudioSocketNumChannels; i++) { - memset(mInputBuffer[i], 0, data.numSamples * sizeof(float)); - memset(mOutputBuffer[i], 0, data.numSamples * sizeof(float)); + // Set up input buffers + bool inputSilenceFlags[AudioSocketNumChannels]; + float* inputBuffers[AudioSocketNumChannels]; + for (int ch = 0; ch < AudioSocketNumChannels; ch++) { + if (ch < data.inputs[0].numChannels) { + inputBuffers[ch] = static_cast(data.inputs[0].channelBuffers32[ch]); + uint64 isSilentFlag = static_cast(1) << ch; + inputSilenceFlags[ch] = (isSilentFlag & data.inputs[0].silenceFlags) != 0; + } else { + inputBuffers[ch] = nullptr; + inputSilenceFlags[ch] = true; + } } - // copy input to buffer - if (mSendMul >= kSilentMul) { - uint64 isSilentFlag = 1; - int channelsIn = min(data.inputs[0].numChannels, AudioSocketNumChannels); - for (int i = 0; i < channelsIn; i++) { - bool isSilent = isSilentFlag & data.inputs[0].silenceFlags; - isSilentFlag <<= 1; - if (isSilent) - continue; - Vst::Sample32* inBuffer = data.inputs[0].channelBuffers32[i]; - for (int j = 0; j < data.numSamples; j++) { - mInputBuffer[i][j] = inBuffer[j] * mSendMul; - } + // Set up output buffers + bool outputSilenceFlags[AudioSocketNumChannels]; + float* outputBuffers[AudioSocketNumChannels]; + for (int ch = 0; ch < AudioSocketNumChannels; ch++) { + if (ch < data.outputs[0].numChannels) { + outputBuffers[ch] = static_cast(data.outputs[0].channelBuffers32[ch]); + } else { + outputBuffers[ch] = nullptr; } } - // send to audio socket - mSocketPtr->compute(data.numSamples, mInputBuffer, mOutputBuffer); + // Process through the audio bridge processor + mProcessor.process(inputBuffers, outputBuffers, inputSilenceFlags, outputSilenceFlags, + data.numSamples); - // copy buffer to output - for (int i = 0; i < data.outputs[0].numChannels; i++) { + // Update silence flags + // Handle any remaining output channels by zeroing them + for (int ch = 0; ch < data.outputs[0].numChannels; ch++) { bool silent = true; - memset(data.outputs[0].channelBuffers32[i], 0, - data.numSamples * sizeof(Vst::Sample32)); - if (mPassMul >= kSilentMul || mRecvMul >= kSilentMul) { - Vst::Sample32* outBuffer = data.outputs[0].channelBuffers32[i]; - for (int j = 0; j < data.numSamples; j++) { - if (i < AudioSocketNumChannels && mRecvMul >= kSilentMul) { - outBuffer[j] = mOutputBuffer[i][j] * mRecvMul; - } - if (i < data.inputs[0].numChannels && mPassMul >= kSilentMul) { - outBuffer[j] += data.inputs[0].channelBuffers32[i][j] * mPassMul; - } - if (silent && outBuffer[j] != 0) { - silent = false; - } - } + if (ch < AudioSocketNumChannels) { + silent = outputSilenceFlags[ch]; + } else { + memset(data.outputs[0].channelBuffers32[ch], 0, + data.numSamples * sizeof(Vst::Sample32)); + silent = true; } if (silent) { - data.outputs[0].silenceFlags |= static_cast(1) << i; + data.outputs[0].silenceFlags |= static_cast(1) << ch; } } return kResultOk; } -//------------------------------------------------------------------------ -float JackTripVSTProcessor::gainToVol(double gain) -{ - // handle min and max - if (gain < kSilentMul) - return 0; - if (gain > 0.9999999) - return 1.0; - // simple logarithmic conversion - return exp(log(1000) * gain) / 1000.0; -} - //------------------------------------------------------------------------ void JackTripVSTProcessor::updateVolumeMultipliers() { // convert [0-1.0] gain (dB) values into [0-1.0] volume multiplers - float outMul = gainToVol(mOutputGain); - mSendMul = gainToVol(mSendGain); - mRecvMul = mOutputMix * outMul; - mPassMul = (1.0f - mOutputMix) * outMul; - - qDebug() << "JackTrip VST send =" << mSendMul << "(" << mSendGain - << "), out =" << outMul << "(" << mOutputGain << "), mix =" << mOutputMix - << ", recv =" << mRecvMul << ", pass =" << mPassMul; + float outMul = AudioBridgeProcessor::gainToVol(mOutputGain); + float sendMul = AudioBridgeProcessor::gainToVol(mSendGain); + float recvMul = mOutputMix * outMul; + float passMul = (1.0f - mOutputMix) * outMul; + + mProcessor.setSendMul(sendMul); + mProcessor.setRecvMul(recvMul); + mProcessor.setPassMul(passMul); + + // qDebug() << "JackTrip VST send =" << sendMul << "(" << mSendGain + // << "), out =" << outMul << "(" << mOutputGain << "), mix =" << mOutputMix + // << ", recv =" << recvMul << ", pass =" << passMul; } //------------------------------------------------------------------------ @@ -509,8 +391,8 @@ tresult PLUGIN_API JackTripVSTProcessor::setupProcessing(Vst::ProcessSetup& newS mSampleRate = newSetup.sampleRate; mBufferSize = static_cast(newSetup.maxSamplesPerBlock); - qDebug() << "JackTrip VST setupProcessing: mSampleRate=" << mSampleRate - << ", mbufferSize=" << mBufferSize; + // qDebug() << "JackTrip VST setupProcessing: mSampleRate=" << mSampleRate + // << ", mbufferSize=" << mBufferSize; //--- called before any processing ---- return AudioEffect::setupProcessing(newSetup); @@ -579,7 +461,7 @@ tresult PLUGIN_API JackTripVSTProcessor::getState(IBStream* state) float sendGain = mSendGain; float outputMix = mOutputMix; float outputGain = mOutputGain; - int8 connectedState = mConnected ? 1 : 0; + int8 connectedState = mProcessor.isEstablished() ? 1 : 0; int32 bypassState = mBypass ? 1 : 0; IBStreamer streamer(state, kLittleEndian); diff --git a/src/vst3/JackTripVSTProcessor.h b/src/vst3/JackTripVSTProcessor.h index a61bf2c..517e7f2 100644 --- a/src/vst3/JackTripVSTProcessor.h +++ b/src/vst3/JackTripVSTProcessor.h @@ -37,11 +37,10 @@ #include #include +#include "../AudioBridgeProcessor.h" #include "public.sdk/source/vst/utility/dataexchange.h" #include "public.sdk/source/vst/vstaudioeffect.h" -class AudioSocket; - //------------------------------------------------------------------------ // JackTripVSTProcessor //------------------------------------------------------------------------ @@ -104,25 +103,19 @@ class JackTripVSTProcessor : public Steinberg::Vst::AudioEffect //------------------------------------------------------------------------ protected: - static float gainToVol(double gain); void updateVolumeMultipliers(); void acquireNewExchangeBlock(); Steinberg::Vst::ParamValue mSendGain = 1.f; Steinberg::Vst::ParamValue mOutputMix = 0; Steinberg::Vst::ParamValue mOutputGain = 1.f; - float mSendMul = 1.f; - float mRecvMul = 0; - float mPassMul = 1.f; bool mConnected = false; bool mBypass = false; private: - QScopedPointer mSocketPtr; + AudioBridgeProcessor mProcessor; QScopedPointer mDataExchangePtr; Steinberg::Vst::DataExchangeBlock mCurrentExchangeBlock; - float** mInputBuffer; - float** mOutputBuffer; Steinberg::Vst::SampleRate mSampleRate = 0; int mBufferSize = 0; }; diff --git a/src/vst3/PkgInfo b/src/vst3/PkgInfo new file mode 100644 index 0000000..19a9cf6 --- /dev/null +++ b/src/vst3/PkgInfo @@ -0,0 +1 @@ +BNDL???? \ No newline at end of file diff --git a/src/vst3/README.md b/src/vst3/README.md new file mode 100644 index 0000000..4068a2e --- /dev/null +++ b/src/vst3/README.md @@ -0,0 +1,73 @@ +# JackTrip VST3 Plugin + +This directory contains the VST3 implementation of the JackTrip audio bridge plugin for cross-platform use in digital audio workstations. + +## Overview + +The JackTrip VST3 plugin provides a bridge between your DAW and remote JackTrip instances, enabling high-quality, low-latency audio exchange over the internet. It uses the AudioBridgeProcessor class to communicate with the JackTrip application running on the same machine. + +## Features + +- **Audio Processing**: Stereo audio input/output with configurable gain controls +- **Network Audio**: Exchange audio with remote JackTrip instances via local socket +- **Parameter Control**: Send gain, output mix, output gain, bypass, and connection status +- **Real-time Audio**: Low-latency audio processing suitable for live performance +- **Cross-platform**: Works on Windows, macOS, and Linux +- **Interface**: Custom VST3 GUI with visual controls and status indicators + +## Files + +- `JackTripPlugin.h/.cpp` - Main VST3 plugin implementation +- `JackTripController.h/.cpp` - Parameter handling and UI communication +- `JackTripProcessor.h/.cpp` - Audio processing engine +- `version.h` - Plugin version information +- `resources/JackTripEditor.uidesc` - VSTGUI interface definition +- `../images/` - Interface graphics (shared with Audio Unit) +- `meson.build` - Build configuration + +## Building + +The VST3 plugin is built automatically when building JackTrip with static Qt: + +```bash +meson setup -Ddefault_library=static -Dnogui=true --buildtype release buildstatic +meson compile -C buildstatic +``` + +## Usage + +1. Launch JackTrip in your desired mode +2. Insert the "JackTrip Audio Bridge" VST3 plugin in your DAW +3. Configure the plugin parameters: + - **Send Gain**: Controls level of audio sent to remote (-60 to +6 dB) + - **Output Mix**: Blends received audio (0%) with input passthrough (100%) + - **Output Gain**: Master output level (-60 to +6 dB) + - **Bypass**: Bypasses all processing + - **Connected**: Shows connection status (read-only) + +The plugin will automatically attempt to connect to the JackTrip application via local socket. + +## Architecture + +The VST3 plugin follows the Steinberg VST3 SDK architecture: + +- `JackTripPlugin` - Main plugin class implementing `IComponent` and `IAudioProcessor` +- `JackTripController` - Parameter controller implementing `IEditController` +- `JackTripProcessor` - Audio processing core with AudioSocket integration +- Uses VSTGUI for cross-platform user interface +- Reuses AudioBridgeProcessor implementation from main application + +## Automation + +All parameters except "Connected" support DAW automation. The plugin reports parameter changes back to the host for automation recording. + +## Requirements + +- **VST3 compatible host** (most modern DAWs) +- **JackTrip application** running on the same machine +- **Operating System**: Windows 10+, macOS 12+, or Linux + +## License + +Copyright (c) 2024-2025 JackTrip Labs, Inc. +Licensed under the MIT License. diff --git a/src/vst3/meson.build b/src/vst3/meson.build new file mode 100644 index 0000000..c2071e0 --- /dev/null +++ b/src/vst3/meson.build @@ -0,0 +1,109 @@ +# JackTrip VST3 Plugin Build Configuration + +# adapted from https://github.com/centricular/gstreamer-vst3 +vst_sdkdir = get_option('vst-sdkdir') +vst_includedir = '@0@/public.sdk/source'.format(vst_sdkdir) +vst_pluginterfaces_includedir = '@0@'.format(vst_sdkdir) +vst_incdirs = [] +vst_incdirs += include_directories('@0@'.format(vst_includedir), is_system: true) +vst_incdirs += include_directories('@0@'.format(vst_pluginterfaces_includedir), is_system: true) +vst_incdirs += include_directories('@0@/vstgui4'.format(vst_pluginterfaces_includedir), is_system: true) + +vst_libdir = get_option('vst-libdir') +if vst_libdir == '' + vst_libdir = vst_sdkdir + '/lib' +endif +libbase_dep = compiler.find_library('base', required : true, dirs : [vst_libdir]) +libsdk_dep = compiler.find_library('sdk', required : true, dirs : [vst_libdir]) +libsdk_common_dep = compiler.find_library('sdk_common', required : true, dirs : [vst_libdir]) +libvstgui_dep = compiler.find_library('vstgui', required : true, dirs : [vst_libdir]) +libvstgui_support_dep = compiler.find_library('vstgui_support', required : true, dirs : [vst_libdir]) +libvstgui_uidescription_dep = compiler.find_library('vstgui_uidescription', required : true, dirs : [vst_libdir]) +libpluginterfaces_dep = compiler.find_library('pluginterfaces', required : true, dirs : [vst_libdir]) +vst_deps = [libbase_dep, libsdk_dep, libsdk_common_dep, libvstgui_dep, libvstgui_uidescription_dep, libvstgui_support_dep, libpluginterfaces_dep] +vst_deps += qt_core_deps + +vst_sources = ['JackTripVSTController.cpp', 'JackTripVSTEntry.cpp', 'JackTripVSTProcessor.cpp'] + +# uncomment for live editor +# vst_sources += ['@0@/vstgui4/vstgui/vstgui_uidescription.cpp'.format(vst_sdkdir), '@0@/vstgui4/vstgui/plugin-bindings/vst3editor.cpp'.format(vst_sdkdir)] +# defines += ['-DVSTGUI_LIVE_EDITING=1'] + +vst_link_args = [] +if (host_machine.system() == 'linux') + vst_sources += '@0@/main/linuxmain.cpp'.format(vst_includedir) + vst_deps += static_deps + vst_sources += static_src + vst_link_args += static_link_args + vst_deps += compiler.find_library('xcb-util', required : true) + vst_deps += compiler.find_library('xcb-cursor', required : true) + vst_deps += compiler.find_library('xkbcommon-x11', required : true) + vst_deps += compiler.find_library('xml2', required : true) + vst_deps += compiler.find_library('cairo', required : true) + vst_deps += compiler.find_library('pango-1.0', required : true) + vst_deps += compiler.find_library('pangocairo-1.0', required : true) + vst_deps += compiler.find_library('expat', required : true) + vst_deps += compiler.find_library('fontconfig', required : true) +elif (host_machine.system() == 'darwin') + vst_sources += '@0@/main/macmain.cpp'.format(vst_includedir) + vst_deps += static_deps + vst_sources += static_src + vst_link_args += static_link_args +elif (host_machine.system() == 'windows') + vst_sources += '@0@/main/dllmain.cpp'.format(vst_includedir) + vst_deps += static_deps + vst_sources += static_src + vst_link_args += static_link_args + vst_deps += compiler.find_library('bcrypt', required : true) + vst_deps += compiler.find_library('winmm', required : true) + vst_deps += compiler.find_library('Crypt32', required : true) + vst_deps += compiler.find_library('ws2_32', required: true) + vst_link_args += 'userenv.lib' + vst_link_args += 'Synchronization.lib' + vst_link_args += 'Netapi32.lib' + vst_link_args += 'Version.lib' + vst_link_args += 'Dwrite.lib' + vst_link_args += 'Iphlpapi.lib' + vst_link_args += 'Secur32.lib' + vst_link_args += 'Winhttp.lib' + vst_link_args += 'Dnsapi.lib' + vst_link_args += 'Iphlpapi.lib' +else + error('Unsupported platform: ' + host_machine.system()) +endif + +if found_libsamplerate + vst_deps += libsamplerate_dep +endif + +audio_socket_moc_h = ['../AudioSocket.h', '../SocketClient.h', '../ProcessPlugin.h'] +audio_socket_sources = qt.compile_moc(headers: audio_socket_moc_h, extra_args: defines) +audio_socket_sources += [ + '../AudioBridgeProcessor.cpp', + '../AudioSocket.cpp', + '../SocketClient.cpp', + '../ProcessPlugin.cpp', + '../jacktrip_globals.cpp' +] + +# Build test executable +audio_socket_test = executable('audio_socket_tests', + ['../../tests/audio_socket_test.cpp'] + audio_socket_sources, + cpp_args : defines, + dependencies : vst_deps, + include_directories: vst_incdirs + include_directories('../'), + link_args: vst_link_args +) + +# Build VST3 plugin +vst3 = shared_module('JackTrip', + vst_sources, audio_socket_sources, + name_prefix: '', + name_suffix: 'vst3', + cpp_args : defines, + dependencies : vst_deps, + include_directories: vst_incdirs, + link_args: vst_link_args +) + +message('JackTrip VST3 plugin will be built') \ No newline at end of file diff --git a/src/vst3/resources/Dual_LED.png b/src/vst3/resources/Dual_LED.png deleted file mode 100644 index 45458f2..0000000 Binary files a/src/vst3/resources/Dual_LED.png and /dev/null differ diff --git a/src/vst3/resources/Sercan_Moog_Knob.png b/src/vst3/resources/Sercan_Moog_Knob.png deleted file mode 100644 index fdab8b5..0000000 Binary files a/src/vst3/resources/Sercan_Moog_Knob.png and /dev/null differ diff --git a/src/vst3/resources/background.png b/src/vst3/resources/background.png deleted file mode 100644 index 2c7effa..0000000 Binary files a/src/vst3/resources/background.png and /dev/null differ diff --git a/src/vst3/resources/background_2x.png b/src/vst3/resources/background_2x.png deleted file mode 100644 index 7e1312d..0000000 Binary files a/src/vst3/resources/background_2x.png and /dev/null differ diff --git a/subprojects/audiounitsdk.wrap b/subprojects/audiounitsdk.wrap new file mode 100644 index 0000000..de8b754 --- /dev/null +++ b/subprojects/audiounitsdk.wrap @@ -0,0 +1,7 @@ +[wrap-git] +url=https://github.com/jacktriplabs/AudioUnitSDK.git +revision=feature/meson-build-file +directory=AudioUnitSDK-1.3.0 + +[provide] +dependency_names = AudioUnitSDK diff --git a/tests/audio_socket_test.cpp b/tests/audio_socket_test.cpp index 57e7776..6d1b622 100644 --- a/tests/audio_socket_test.cpp +++ b/tests/audio_socket_test.cpp @@ -84,10 +84,7 @@ int main(int argc, char** argv) QCoreApplication app(argc, argv); AudioSocket s; - if (!s.connect(SAMPLE_RATE, BUFFER_SIZE)) { - cerr << "Failed to connect: " << s.getSocket().errorString().toStdString() << endl; - return -1; - } + s.connect(SAMPLE_RATE, BUFFER_SIZE); s.setRetryConnection(true); MyThread thread(s); diff --git a/win/CodeSignTool/CodeSignTool.sh b/win/CodeSignTool/CodeSignTool.sh deleted file mode 100755 index 10c4bc9..0000000 --- a/win/CodeSignTool/CodeSignTool.sh +++ /dev/null @@ -1 +0,0 @@ -java -cp "./jar/picocli-4.6.1.jar:./jar/bcprov-jdk15on-1.65.01.jar:./jar/httpclient-4.5.13.jar:./jar/json-simple-1.1.1.jar:./jar/jsign-core-3.1.jar:./jar/commons-io-2.8.0.jar:./jar/bcpkix-jdk15on-1.65.jar:./jar/code_sign_tool-1.2.2.jar:./jar/httpcore-4.4.13.jar:./jar/commons-logging-1.2.jar:./jar/log4j-api-2.17.1.jar:./jar/log4j-core-2.17.1.jar:./jar/poi-4.1.2.jar:./jar/commons-lang3-3.9.jar:./jar/commons-math3-3.6.1.jar:./jar/totp-1.0.jar:./jar/commons-codec-1.15.jar" com.ssl.code.signing.tool.CodeSignTool $@ diff --git a/win/CodeSignTool/conf/code_sign_tool.properties b/win/CodeSignTool/conf/code_sign_tool.properties deleted file mode 100644 index 5622896..0000000 --- a/win/CodeSignTool/conf/code_sign_tool.properties +++ /dev/null @@ -1,4 +0,0 @@ -CLIENT_ID=kaXTRACNijSWsFdRKg_KAfD3fqrBlzMbWs6TwWHwAn8 -OAUTH2_ENDPOINT=https://login.ssl.com/oauth2/token -CSC_API_ENDPOINT=https://cs.ssl.com -TSA_URL=http://ts.ssl.com \ No newline at end of file diff --git a/win/CodeSignTool/conf/log4j2.xml b/win/CodeSignTool/conf/log4j2.xml deleted file mode 100644 index e3ee419..0000000 --- a/win/CodeSignTool/conf/log4j2.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n - - - - - - ${LOG_PATTERN} - - - - - - - - - - - - - - \ No newline at end of file diff --git a/win/build_installer.bat b/win/build_installer.bat index f70c847..34178d8 100755 --- a/win/build_installer.bat +++ b/win/build_installer.bat @@ -22,31 +22,29 @@ xcopy ..\LICENSES deploy\LICENSES\ REM create RTF file with licenses' text set LICENSEPATH=deploy\license.rtf -echo {\rtf1\ansi\deff0 {\fonttbl {\f0 Calibri;}} \f0\fs22>%LICENSEPATH% -for %%f in (..\LICENSE.md ..\LICENSES\MIT.txt ..\LICENSES\GPL-3.0.txt ..\LICENSES\LGPL-3.0-only.txt ..\LICENSES\AVC.txt) do ( - for /f "delims=" %%x in ('type %%f') do ( - echo %%x\line>>%LICENSEPATH% - ) - echo \par >>%LICENSEPATH% -) -echo }>>%LICENSEPATH% +pandoc -s -f markdown -t rtf -o deploy\license.rtf ..\LICENSE.md if "%~1"=="/q" ( - copy dialog_alt.bmp deploy\dialog.bmp + copy dialog_alt.bmp deploy\dialog.bmp ) else ( - copy dialog.bmp deploy\ + copy dialog.bmp deploy\ ) + if exist ..\builddir\release\jacktrip.exe (set JACKTRIP=..\builddir\release\jacktrip.exe) else (set JACKTRIP=..\builddir\jacktrip.exe) copy %JACKTRIP% deploy\ -if exist ..\builddir\JackTrip.vst3 ( - echo Including JackTrip.vst3 +if exist ..\buildstatic\src\vst3\JackTrip.vst3 ( + echo Including JackTrip.vst3 mkdir deploy\JackTrip.vst3 mkdir deploy\JackTrip.vst3\Contents xcopy /E ..\src\vst3\resources deploy\JackTrip.vst3\Contents\Resources\ + copy ..\src\images\background.png deploy\JackTrip.vst3\Contents\Resources\ + copy ..\src\images\background_2x.png deploy\JackTrip.vst3\Contents\Resources\ + copy ..\src\images\Sercan_Moog_Knob.png deploy\JackTrip.vst3\Contents\Resources\ + copy ..\src\images\Dual_LED.png deploy\JackTrip.vst3\Contents\Resources\ copy ..\LICENSE.md deploy\JackTrip.vst3\Contents\Resources\LICENSE.md xcopy /E ..\LICENSES deploy\JackTrip.vst3\Contents\Resources\LICENSES\ mkdir deploy\JackTrip.vst3\Contents\x86_64-win - copy ..\builddir\JackTrip.vst3 deploy\JackTrip.vst3\Contents\x86_64-win\JackTrip.vst3 + copy ..\buildstatic\src\vst3\JackTrip.vst3 deploy\JackTrip.vst3\Contents\x86_64-win\JackTrip.vst3 ) cd deploy @@ -83,15 +81,39 @@ if %ERRORLEVEL% NEQ 0 ( rem Get our version number for /f "tokens=*" %%a in ('.\jacktrip -v ^| findstr VERSION') do for %%b in (%%~a) do set VERSION=%%b -for /f "tokens=1 delims=-" %%a in ("%VERSION%") do set VERSION=%%a -echo Version=%VERSION% +rem Convert semantic version to numeric for Windows Installer +for /f "tokens=1,2 delims=-" %%a in ("%VERSION%") do ( + set VERSION_NUM=%%a + set VERSION_SUFFIX=%%b +) +if "%VERSION_SUFFIX%"=="" ( + set VERSION_NUM=%VERSION_NUM%.100 +) else ( + rem Extract beta number from betaN using string replacement + set BETA_NUM=%VERSION_SUFFIX% + set BETA_NUM=!BETA_NUM:beta=! + if not "!BETA_NUM!"=="%VERSION_SUFFIX%" ( + set VERSION_NUM=%VERSION_NUM%.!BETA_NUM! + ) else ( + rem Handle other suffixes like rc1, etc. + set VERSION_NUM=%VERSION_NUM%.50 + ) +) + +echo Version=%VERSION% (Windows Installer: %VERSION_NUM%) + +rem Build the MSI installer if exist JackTrip.vst3 ( - powershell -Command "(gc JackTrip.vst3\Contents\Resources\moduleinfo.json) -replace '%%VERSION%%', '%VERSION%' | Out-File -encoding ASCII JackTrip.vst3\Contents\Resources\moduleinfo.json" - candle.exe -arch x64 -ext WixUIExtension -ext WixUtilExtension -dvst=true -dVersion=%VERSION%%WIXDEFINES% ..\jacktrip.wxs ..\jacktrip-vst3.wxs ..\qt%QTVERSION%.wxs + powershell -Command "(gc JackTrip.vst3\Contents\Resources\moduleinfo.json) -replace '%%VERSION_NUM%%', '%VERSION_NUM%' | Out-File -encoding ASCII JackTrip.vst3\Contents\Resources\moduleinfo.json" + candle.exe -arch x64 -ext WixUIExtension -ext WixUtilExtension -dvst=true -dVersion=%VERSION_NUM%%WIXDEFINES% ..\jacktrip.wxs ..\jacktrip-vst3.wxs ..\qt%QTVERSION%.wxs light.exe -ext WixUIExtension -ext WixUtilExtension -o JackTrip.msi jacktrip.wixobj jacktrip-vst3.wixobj qt%QTVERSION%.wixobj ) else ( - candle.exe -arch x64 -ext WixUIExtension -ext WixUtilExtension -dVersion=%VERSION%%WIXDEFINES% ..\jacktrip.wxs ..\qt%QTVERSION%.wxs + candle.exe -arch x64 -ext WixUIExtension -ext WixUtilExtension -dVersion=%VERSION_NUM%%WIXDEFINES% ..\jacktrip.wxs ..\qt%QTVERSION%.wxs light.exe -ext WixUIExtension -ext WixUtilExtension -o JackTrip.msi jacktrip.wixobj qt%QTVERSION%.wixobj ) + +rem Compile the bundle but don't build it yet +candle.exe -arch x64 -ext WixBalExtension -dVersion=%VERSION_NUM% ..\jacktrip-bundle.wxs + endlocal diff --git a/win/jacktrip-bundle.wxs b/win/jacktrip-bundle.wxs new file mode 100644 index 0000000..3dfb39d --- /dev/null +++ b/win/jacktrip-bundle.wxs @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/win/jacktrip.wxs b/win/jacktrip.wxs index e48cdf3..11abf2c 100644 --- a/win/jacktrip.wxs +++ b/win/jacktrip.wxs @@ -10,6 +10,7 @@ InstallerVersion='200' Languages='1033' Compressed='yes' SummaryCodepage='1252' /> + @@ -46,21 +47,20 @@ - - + - WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 and NOT Installed - + + + + NOT Installed + diff --git a/win/meson.build b/win/meson.build index 7362bba..b9d11a4 100644 --- a/win/meson.build +++ b/win/meson.build @@ -33,4 +33,6 @@ if host_machine.system() == 'windows' link_args += 'Iphlpapi.lib' endif + uninstall_old_jacktrip = executable('uninstall_old_jacktrip', 'uninstall_old_jacktrip.cpp', include_directories: incdirs, dependencies: deps, link_args: link_args, c_args: c_defines, cpp_args: defines, install: false ) + endif diff --git a/win/qt6-noguids.wxs b/win/qt6-noguids.wxs index 00e4918..cdd7a4e 100644 --- a/win/qt6-noguids.wxs +++ b/win/qt6-noguids.wxs @@ -1,2226 +1,4712 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/win/qt6.wxs b/win/qt6.wxs index 583b447..1b719a9 100644 --- a/win/qt6.wxs +++ b/win/qt6.wxs @@ -12,6 +12,7 @@ + @@ -46,6 +47,9 @@ + + + @@ -58,8 +62,8 @@ - - + + @@ -67,25 +71,58 @@ - - - + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + - + - + @@ -94,9 +131,6 @@ - - - @@ -106,24 +140,21 @@ - - - + + + - - - @@ -184,6 +215,21 @@ + + + + + + + + + + + + + + + @@ -298,20 +344,14 @@ + + + - - - - - - - - - - - + + @@ -331,15 +371,6 @@ - - - - - - - - - @@ -379,194 +410,2702 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + @@ -925,8 +3464,8 @@ - - + + @@ -1330,15 +3869,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -1354,9 +3917,18 @@ + + + + + + + + + @@ -1369,169 +3941,208 @@ + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -1552,15 +4163,6 @@ - - - - - - - - - @@ -1570,57 +4172,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1639,39 +4190,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1687,8 +4205,8 @@ - - + + @@ -1699,57 +4217,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1807,8 +4274,11 @@ - - + + + + + @@ -1980,6 +4450,16 @@ + + + + + + + + + + @@ -2001,8 +4481,8 @@ - - + + @@ -2015,11 +4495,6 @@ - - - - - @@ -2041,8 +4516,13 @@ - - + + + + + + + @@ -2055,6 +4535,16 @@ + + + + + + + + + + @@ -2070,11 +4560,31 @@ + + + + + + + + + + + + + + + + + + + + @@ -2095,6 +4605,11 @@ + + + + + @@ -2130,11 +4645,6 @@ - - - - - @@ -2150,11 +4660,6 @@ - - - - - @@ -2165,6 +4670,11 @@ + + + + + @@ -2180,16 +4690,6 @@ - - - - - - - - - - diff --git a/win/uninstall_old_jacktrip.cpp b/win/uninstall_old_jacktrip.cpp new file mode 100644 index 0000000..6ec6260 --- /dev/null +++ b/win/uninstall_old_jacktrip.cpp @@ -0,0 +1,255 @@ +#include +#include +#include +#include +#include +#include + +// Structure to hold JackTrip product information +struct JackTripProduct { + std::string productCode; + std::string displayName; + std::string publisher; +}; + +// Function to get current timestamp for logging +std::string GetTimestamp() { + SYSTEMTIME st; + GetLocalTime(&st); + std::ostringstream oss; + oss << std::setfill('0') << std::setw(4) << st.wYear << "-" + << std::setfill('0') << std::setw(2) << st.wMonth << "-" + << std::setfill('0') << std::setw(2) << st.wDay << " " + << std::setfill('0') << std::setw(2) << st.wHour << ":" + << std::setfill('0') << std::setw(2) << st.wMinute << ":" + << std::setfill('0') << std::setw(2) << st.wSecond; + return oss.str(); +} + +// Function to log messages with timestamp +void LogMessage(const std::string& message) { + std::cout << "[" << GetTimestamp() << "] " << message << std::endl; +} + +// Function to check if a registry key exists +bool RegKeyExists(HKEY hKey, const std::string& subKey) { + HKEY hSubKey; + LONG result = RegOpenKeyExA(hKey, subKey.c_str(), 0, KEY_READ, &hSubKey); + if (result == ERROR_SUCCESS) { + RegCloseKey(hSubKey); + return true; + } + return false; +} + +// Function to get a DWORD value from registry +bool GetRegDWORD(HKEY hKey, const std::string& subKey, const std::string& valueName, DWORD& value) { + HKEY hSubKey; + LONG result = RegOpenKeyExA(hKey, subKey.c_str(), 0, KEY_READ, &hSubKey); + if (result != ERROR_SUCCESS) { + return false; + } + + DWORD dataSize = sizeof(DWORD); + DWORD dataType = REG_DWORD; + result = RegQueryValueExA(hSubKey, valueName.c_str(), NULL, &dataType, (LPBYTE)&value, &dataSize); + RegCloseKey(hSubKey); + + return (result == ERROR_SUCCESS && dataType == REG_DWORD); +} + +// Function to get a string value from registry +bool GetRegString(HKEY hKey, const std::string& subKey, const std::string& valueName, std::string& value) { + HKEY hSubKey; + LONG result = RegOpenKeyExA(hKey, subKey.c_str(), 0, KEY_READ, &hSubKey); + if (result != ERROR_SUCCESS) { + return false; + } + + DWORD dataSize = 0; + DWORD dataType = REG_SZ; + result = RegQueryValueExA(hSubKey, valueName.c_str(), NULL, &dataType, NULL, &dataSize); + if (result != ERROR_SUCCESS) { + RegCloseKey(hSubKey); + return false; + } + + std::vector buffer(dataSize); + result = RegQueryValueExA(hSubKey, valueName.c_str(), NULL, &dataType, (LPBYTE)buffer.data(), &dataSize); + RegCloseKey(hSubKey); + + if (result == ERROR_SUCCESS && dataType == REG_SZ) { + value = std::string(buffer.data()); + return true; + } + + return false; +} + +// Function to enumerate registry subkeys +std::vector EnumRegSubKeys(HKEY hKey, const std::string& subKey) { + std::vector subKeys; + HKEY hSubKey; + + LONG result = RegOpenKeyExA(hKey, subKey.c_str(), 0, KEY_ENUMERATE_SUB_KEYS, &hSubKey); + if (result != ERROR_SUCCESS) { + return subKeys; + } + + char keyName[256]; + DWORD keyNameSize = sizeof(keyName); + DWORD index = 0; + + while (RegEnumKeyExA(hSubKey, index, keyName, &keyNameSize, NULL, NULL, NULL, NULL) == ERROR_SUCCESS) { + subKeys.push_back(std::string(keyName)); + keyNameSize = sizeof(keyName); + index++; + } + + RegCloseKey(hSubKey); + return subKeys; +} + +// Function to test if JackTrip version is old (2.7.0 or earlier) +bool TestJackTripVersion(const std::string& productCode) { + std::string regPath = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\" + productCode; + + // Check if this is a Windows Installer (MSI) installation + DWORD windowsInstaller = 0; + if (!GetRegDWORD(HKEY_LOCAL_MACHINE, regPath, "WindowsInstaller", windowsInstaller) || windowsInstaller != 1) { + LogMessage("Product " + productCode + " is not a Windows Installer (MSI) installation"); + return false; + } + + // Get Version DWORD value + DWORD versionDword = 0; + if (!GetRegDWORD(HKEY_LOCAL_MACHINE, regPath, "Version", versionDword)) { + LogMessage("No Version DWORD found for product " + productCode); + return false; + } + + LogMessage("Found Version DWORD: " + std::to_string(versionDword) + " for product " + productCode); + + // Extract version components from DWORD + // First byte = major, second byte = minor, third byte = build, fourth byte = patch + DWORD major = (versionDword & 0xFF000000) >> 24; + DWORD minor = (versionDword & 0x00FF0000) >> 16; + DWORD build = (versionDword & 0x0000FF00) >> 8; + DWORD patch = (versionDword & 0x000000FF); + + LogMessage("Parsed version as major=" + std::to_string(major) + + ", minor=" + std::to_string(minor) + + ", patch=" + std::to_string(patch)); + + // Check if version is 2.7.0 or earlier + if (major < 2 || (major == 2 && minor < 7) || (major == 2 && minor == 7 && patch == 0)) { + return true; + } + + return false; +} + +// Function to uninstall JackTrip product using msiexec +bool UninstallJackTripProduct(const std::string& productCode) { + LogMessage("Attempting to uninstall JackTrip product: " + productCode); + + // Build msiexec command + std::string command = "msiexec.exe /x \"" + productCode + "\" /quiet /norestart"; + LogMessage("Executing: " + command); + + // Execute the command + STARTUPINFOA si = {0}; + PROCESS_INFORMATION pi = {0}; + si.cb = sizeof(si); + + // Convert command to char array for CreateProcessA + std::vector cmdBuffer(command.begin(), command.end()); + cmdBuffer.push_back('\0'); + + BOOL result = CreateProcessA( + NULL, // No module name (use command line) + cmdBuffer.data(), // Command line + NULL, // Process handle not inheritable + NULL, // Thread handle not inheritable + FALSE, // Set handle inheritance to FALSE + 0, // No creation flags + NULL, // Use parent's environment block + NULL, // Use parent's starting directory + &si, // Pointer to STARTUPINFO structure + &pi // Pointer to PROCESS_INFORMATION structure + ); + + if (!result) { + LogMessage("Failed to start uninstall process for product " + productCode); + return false; + } + + // Wait for the process to complete + WaitForSingleObject(pi.hProcess, INFINITE); + + // Get exit code + DWORD exitCode = 0; + GetExitCodeProcess(pi.hProcess, &exitCode); + + // Clean up + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + + if (exitCode == 0) { + LogMessage("Successfully uninstalled JackTrip product: " + productCode); + return true; + } else { + LogMessage("Uninstall failed for product " + productCode + " with exit code: " + std::to_string(exitCode)); + return false; + } +} + +int main() { + LogMessage("Starting JackTrip old version detection and uninstall process"); + + // Query registry for JackTrip installations + std::vector jacktripProducts; + std::vector subKeys = EnumRegSubKeys(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall"); + + for (const auto& subKey : subKeys) { + std::string regPath = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\" + subKey; + + std::string displayName, publisher; + if (GetRegString(HKEY_LOCAL_MACHINE, regPath, "DisplayName", displayName) && + GetRegString(HKEY_LOCAL_MACHINE, regPath, "Publisher", publisher)) { + + if (displayName == "JackTrip" && publisher == "JackTrip") { + JackTripProduct product; + product.productCode = subKey; + product.displayName = displayName; + product.publisher = publisher; + jacktripProducts.push_back(product); + LogMessage("Found JackTrip installation: " + subKey); + } + } + } + + if (jacktripProducts.empty()) { + LogMessage("No JackTrip installations found in registry"); + return 0; + } + + LogMessage("Found " + std::to_string(jacktripProducts.size()) + " JackTrip installation(s)"); + + // Check each product for old version and uninstall if needed + int uninstalledCount = 0; + + for (const auto& product : jacktripProducts) { + if (TestJackTripVersion(product.productCode)) { + LogMessage("Old JackTrip version detected: " + product.productCode); + if (UninstallJackTripProduct(product.productCode)) { + uninstalledCount++; + } + } else { + LogMessage("JackTrip installation " + product.productCode + " is not an old version (v2.7.0 or earlier) or is not an MSI installation"); + } + } + + LogMessage("Uninstall process completed. Uninstalled " + std::to_string(uninstalledCount) + " old JackTrip installation(s)"); + return 0; +} \ No newline at end of file